エンジニア的なネタを毎週書くブログ

東京でWebサービスの開発をしています 【英語版やってみました】http://taichiw-e.hatenablog.com/

"Fiber" で 100万スレッドが実行可能に!? - Project Valhalla とProject Loom : JavaDayTokyo 2018 参加レポート2 #JavaDayTokyo

Project XXX系の話について、Project ValhallaとProject Loomについて聞きました。
どちらも講演はDavid Buckさん*1

残念ながら、どちらもJava11には入らないので、LTSのリリースサイクルを考えるとまともにサービスで使えるのは早くて2021年…
とはいえ、どちらもとても夢のある話でした。

Project Valhalla

Value Types (値型)

class Point {
   int x;
   int y;
}

のような、プリミティブ型をフィールドとして持つクラスがある時に

  Point[] points;

配列を作ると、各Pointインスタンス毎にヘッダを持ってしまい、大きなメモリのオーバーヘッドが発生します。
メモリの最適化だけ考えると

  int[] xList;
  int[] yList;

とプリミティブ変数の配列を宣言するのが最も良いのですが、これではせっかくのオブジェクト指向が台無し。

そこで、「例えば*2

value class Point {
   int x;
   int y;
}

このようにクラス宣言した際に、さも intの配列が2つあるかのようなメモリ確保がされる… というのが概要です。
これによって、省メモリ化に加えて、参照の高速化も図ることができるようになります。

ここまでは昨年のJavaDayTokyoでも既に紹介されていました。

Generic Specialization (ジェネリックの特殊化)

上記を実現する過程で、このようなことができる様になる… と紹介されました。

ArrayList<int> xList;

要はプリミティブ型がほとんど普通のオブジェクトのように使えると。
さらに、intStreamのような(普通のstreamと別にメソッドを用意しなくてはならないような)「ダサい」対応も不要になるとのことです。

とは言えいろいろ悩んでるらしい

とは言え、この「ジェネリックの特殊化」をいざ実装しようとすると悩ましい課題が多いらしく… まだまだ実用化には時間がかかりそうでした。

実際の所、どのくらい使えるんだろう

現実的な利用シーンを考えると、フィールドにプリミティブしか持たないクラスというのはあんまりない気も。
せめて、StringやLocalDate系が混ざっていてもうまいこと扱っていただけるようになると良さそうなのですが。

Project Loom

こちらがタイトルの、”100万スレッド”です。
現在のJVMでは、JVM上のスレッドは、OSが管理するスレッドとマッピングされています。それぞれのスレッドは、用途が異なるかもしれません。あるスレッドは複雑な暗号化に使われるかもしれないし、またあるスレッドは単に変数を一つ書き換えるだけの簡単な処理に使われるかもしれません。

しかしながらOSは、アプリケーションのコードを知らないため、各スレッドの用途もわかりません。そのため、どのスレッドも一律、「何にでも使えるスレッド」を用意することになります。

ここで、「Fiber」という考え方が登場します。Fiberは、OSではなく、Javaのruntimeやユーザコードによって支配されるスレッド。アプリケーションはスレッドの用途を知っているため、無駄のないスレッドを効率的に作ることが可能です。その結果、OS管理のスレッドに比べてなんと1000倍ほど数のスレッドが作れるようになるとのこと。
それだけOSが作るスレッドには「無駄」があるそうです。

ブロッキングと非同期

ちょうど一年前のJavaDayTokyoで聞いた話ですが、
taichiw.hatenablog.com
ブロッキングな処理はスケールしにくい、という問題点があります。
その対策として、非同期(ノンブロッキング)に処理を制御する、という方法があるものの、プログラミングやデバッギングが難しい、という問題があります。

この双方の問題を解決するのがFiber.
スレッドモデル(=ブロッキングな処理)であるため取扱が簡単であるにもかかわらず、スケーラビリティに優れるという夢のようなお話です。

言ってることが未来

Fiberはスレッドなので、「どんな処理が」「どこまで進んだか」が管理されています。
他方、JVM上で管理されているオブジェクトなので、Serializeが可能とのこと。
その結果何ができるようになるかと言うと…

  • 実行中のスレッドをSerializeして保存。JVM停止。→JVMを起動してスレッドの続きを実行
  • 分散データベースに於いて、「処理を他のサーバに転送」

と言ったことができるようになるとのこと。

… よくわかりません。

まだ検討段階

実際の所、まだまだ開発が始まったばかりでCommitもないとか。
早くて今年中にアーリーアクセスが出る…… かも。

とのことでした。

しかしとっても夢のある話。実現が楽しみです。

*1:基調講演でFlight Recorderのデモもされて… 来週末の JJUG CCCでもセッションがあるそうで… 寝不足で頭が回ってないといってましたがこれだけ準備してたら、そらそうでしょうなぁ…

*2:まだ決まったわけではない… と強調されてました

Java11について : JavaDayTokyo 2018 参加レポート1 #JavaDayTokyo

今年もJavaDayTokyoに参加させていただきました。

今年の自分なりのテーマは、以下についての情報収集でした。

  • 半年後にリリースされるLTSバージョンであるJava11の新機能
  • その他今後追加される機能について
    • 特に、一昨年くらいから聞いているProject Valhalla。結構な数のコレクションをゴリゴリ処理するのが最近の自分のお仕事の大事なところなので、どうなったか興味アリ

このエントリでは主にJava 11についてまとめます。

Java 10とJava 11 の差

Java 10 からJava 11への追加機能として、現在8件のJPEが予定されているそうです。
http://openjdk.java.net/projects/jdk/11/

この内、コードの書き方に影響がありそうな、文法面での変更は、Project Amberの一つでもあるこれだけかなぁ…と。
JEP 323: Local-Variable Syntax for Lambda Parameters

ということで、Java8 → Java11の差は雑に言うと、
Project Jigsaw + "var"
と言っていいかなぁ… という気がしました。

櫻庭 祐一 さんのセッション、「Java SE 10、そしてJava SE 11への移行ガイド」でも、8つのチェックポイントが紹介されていましたが、ハマりどころになるのは概ねJigsaw絡みと感じました。

Ask the Experts! でもそのような回答だったようです。

11 の次の LTS バージョンは 17 !

Java 11の次のLTSバージョンは、2021年9月にリリースされるJava17とのこと。
私、1年半に一回LTSが出るものだと思っていたんですが、3年後なんですね。オリンピック終わっちゃってるのかぁ…

Mission Control と Flight RecorderがJDK11からオープンソース

これまで有償ツールだったMission ControlとFlight Recorderが、JDK11から無償で使えるようになるとのこと。
基調講演内でデモがあったのですが、パフォーマンスの問題を見つけるのにずいぶん使えそうに見えました。
気になる…!

一つのドキュメントに、いろいろ一緒に書くのは難しかった話

・ビジネス上の要件
・自分のところのAPIはどう振る舞うべきか
・どうしてそう決めたか(ちょっともとの要件からねじれてる)
・中はどう作るべきか

を、全部一箇所に書くのは結局無理でした。

なので
・ビジネス上の要件
・自分のところのAPIはどう振る舞うべきか(の、一段階抽象化したやつ)
だけを書いて

・どうしてそう決めたか → こうすれば「想定されてるケース全部うまくいくから亅で逃げる
・本当のAPIの振る舞い → 別に書いたテストで表現
・どう作るか → 上のテストが通れば何でもいいよ

という体裁に。
俺が伝えるべきことは伝えられるドキュメントになったと思うけど、
俺は一体何屋なんだ(笑)

チームビルディングのために性格診断を使ってみたい

チームビルディングのために性格診断を使ってみたい …と思っています。

背景

今、一緒に開発をしているチームのコミュニケーションを、より円滑にしたいと考えています。
各メンバーが、もっと他のチームメンバーの、「良いところ」や「自分との違い(どっちが良い悪いではない)」を発見することによって、より良いコミュニケーションと、そこから生まれるチームワークが実現できると考えています。

ジョハリの窓

私が、チーム内のコミュニケーションがより円滑になると考えている根拠が、ジョハリの窓です。
ジョハリの窓 - Wikipedia
f:id:taichiw:20180429170038p:plain

IとIIの間の敷居、IとIIIの間の敷居を移動させて「公開された自己」の領域が広がることでコミュニケーションが円滑になる、という理論。
このうち、IとIIIの間の敷居を移動させ、Iの領域を広げるために、性格診断が使えそうというアイディアです。
f:id:taichiw:20180429170458p:plain

きっかけ

一番始めにこの手のことを考えたのは一昨年のXPまつり。
「ピンポンゲームを使ったチームビルディング」の話を新井さんから聞いたときでした。
残念ながら記録を残していなかったので記憶ベースの話になりますが…

  • ゲーム内で、自身の自己開示を進める。例えば
    • 「自分はプレッシャーに弱い」
    • チームの勝利は大事だが、自身の記録も大事にしたい
  • ゲーム内でチームメンバーの性格を把握したことが、業務内でのコミュニケーションに生きる

といった内容だったと思います。
これすごく良い!試そう!と思った記憶があります。(思っただけで試さなかったのですが…)

そして1年半ほど経過した先日、「ふりかえりワークショップ*1」に参加した際、今度は、
性格診断を使って自己開示する
というアイディアを聴きました。(このときに、前述のピンポンゲームの話も久しぶりに思い出しました…)

今の私のチームは、メンバーが半々ほどで東京と上海に分かれており、アジャイルのゲームのように同じ場にいることを前提としたアクティビティを使うことが難しいです。
そこで、この性格診断を使った方法、ありなんじゃないかな? と思った次第です。

こんな進め方を考えています

性格診断は、
MBTI理論をもとにしている、
無料性格診断テスト | 16Personalities
の利用を考えています。

  • ふりかえりワークショップで教えてもらったのがMBTIだったこと
  • 上記サイトは非常に多くの言語をサポートしていること(現在のチームは多国籍で、母国語が異なるメンバーで構成されています)

が理由です。

事前準備

・性格診断を受ける
・結果の中であたってる!と思ったところを抜粋
・具体的なエピソードを思い出し、上記抜粋と併記

ここまでを、事前準備として参加者に行っておいてもらいます。

なお、前述のとおり私の今のチームは母国語が異なるメンバーで構成されているため、
性格診断と結果の確認は母国語版で、
共有のための書き出しは英語版で行ってもらうことを想定しています。

自己の開示

上記、準備したものを参加者それぞれに共有してもらいます。
この共有の仕方が実は悩みどころでして…

真っ先に思いついたのは、全員同じ場で(拠点はビデオ会議でつないで)、順番に発表してもらう、「同期」方法です。
ただ、この方法ですと、他の人は基本的に受け身になってしまう、という問題があります。
全部で9人いるので、一人3分としても27分、5分なら45分。順番に聞いていくのってなかなか疲れますよね。
他の参加者が発表者に対してフィードバックを行うことも考えたのですが、できるとしたら

  • たしかにあなたはそういうところがありますね、という同調。
  • 発展させて、他にもこういう事例もありますよね、という「事例の追加」

あたりでしょうか。

続いて、非同期な方法も考えてみました。
せっかく事前準備をしてもらうので、事前準備を集めて共有し、あとは各メンバーに読んでもらうという方法です。
こちらは、「ちゃんと読んでくれるかなぁ…」 という懸念が募ります。

言葉ではない方法で共有する方法も考えました。
こちらのブログでは、結果を四象限上にマッピングしているようで、
qiita.com
このような形で、パッと見でメンバー9人全員の結果がわかるような形にできれば、そこから興味を持って話を聞くなり読むなりしてくれそうです。

とりあえず一回やってみたい

ということで、いきなりチームでやると難しそうなので、まずはリハーサルをやってみたいなと思っています。
付き合ってくれそうな方に声をかけて、やらせてもらおうかなぁと思っています。
(もしくはご興味がある方、是非一緒にやりましょう)

ちなみに私の結果

“仲介者”型の性格 (INFP-A / INFP-T) | 16Personalities
でした。

あたっているな、と思ったのは以下の点です。

  • 「同じ考えを持つ人々を見つけて共に過ごす時、そこで感じる調和が、仲介者型にとって、喜び」

→ 逆に、ある集団内で意見が対立していると、大変居心地が悪い。健全な組織のために時には意見対立も必要だ…と思いつつ、中間状態を見ているのがめちゃ辛い。*2

  • 「難なく比喩表現やたとえ話を用い、シンボルを理解し生み出しながら、自分の考えを共有できます」

→ 比較的複雑な概念、難しい概念を、わかった気にさせる『デフォルメ』は得意な方だと思う

  • 「一度に多くのことをやろうとすると力尽き」

→ やることが複数になるとすぐパニックになります

  • 「生きて行く上で欠かせない日常生活の維持を疎かにしてしまう可能性もあるので注意しましょう」

→ 特に『ノッてる』ときは、食事すらしたくないときがある。*3

  • 「放っておくと、まるで隠者のように引きこもったまま連絡が途絶え」

→ 『俺のこと放っておいてほしい』とき、よくあります。ひとりで考え事に集中したいです。*4

超余談

この投稿が、ちょうど本ブログの100記事目だったようです。
2011年11月、サンフランシスコへの長期研修中に始めて、ここまで約6年半かかりました。
タイトルのとおり毎週書いていたら、わずか2年弱で達成されていたはずなのですが、3倍半程かかりました(笑)

*1:別途ブログを書く予定です! …というか書いてる途中で、こちらの記事が独立してしまいました

*2:チームの仲が悪い と感じているのもここから来ているのかも。違う性格の人であれば、「普通だな」くらいに思うのかもしれません

*3:なので、会社で誰かとランチの約束をするのが苦手だったりします。でも寂しがり屋です。メンドウですね

*4:でも、かまってほしい寂しがり屋です。メンドウで(略)

続・続・計算量を減らす:Newの回数を減らしたらもっと速く… なるどころか遅くなった!?

前回までのお話
taichiw.hatenablog.com
たった一行、乱数の初期化をループの中からクラス生成時に移しただけで5倍も速くなったというお話でした。
f:id:taichiw:20180422065736p:plain

じゃあ、クラス変数にしたらNewが減るじゃん!ということでstaticをつけてみました。

private static Random rnd = new SecureRandom();

結果。
37秒, 39秒, 45秒, 49秒, 49秒, …

って… 元々30秒だったのが遅くなってるじゃないですカー!

CPUがサボってやがる

CPUの使われ方を比較してみると

インスタンス変数(=Classが作られるタイミングでNewする場合)
f:id:taichiw:20180424010047p:plain

クラス変数(= Classがロードされるタイミングで一回だけNewする場合)
f:id:taichiw:20180424005836p:plain

クラス変数のほうがギザギザしていて、天井に張り付いて『ない』気が。
(上のスクリーンショットだとあまりそうは見えませんが… 動いているものを見るとそんな気がしました) 

おそらくこのサボりが遅さの原因… 

ということは、マルチスレッド間でアクセスの待ちが発生している?

と思い調べてみると、案の定でした。

SecureRandomクラスでは、

    public int nextInt(int bound) {

から呼ばれている

    @Override
    final protected int next(int numBits) {

から呼ばれている

    @Override
    public void nextBytes(byte[] bytes) {

から呼ばれている

    public synchronized void engineNextBytes(byte[] var1) {

こいつがsynchronized。
ということで、マルチスレッド間で1インスタンスを共有した結果、メソッドにロックがかかって待ちが発生し、遅くなってしまっていたのでした。

SecureRandomをやめたら…

これまで、なんとなくSecureRandomクラスを使っていたのですが*1、改めて見るといかにも「Secureを得るために速度を犠牲にしている」っぽい名前です。
そこで改めて確認したところ、SecureRandomクラスの他に、単なるRandomクラスが存在しました*2

Randomクラスで用いられている乱数生成アルゴリズムは予測可能であるために、暗号などには使えないそうなのですが、別に暗号を作っているわけではないのでこれで十分。

ということで、ただのRandomクラスに差し替えてみます。
f:id:taichiw:20180424012701p:plain

22秒、21秒、20秒、22秒、…

おおおお… 速くなった!!

しかも、クラス変数でも、インスタンス変数でも、はたまたループ内で都度生成されるメソッドローカルな変数でも、ほぼ実行時間が変わらないという。
要は、SecureRandomの生成が遅かったのか…。

おまけ そもそも乱数を使わなかったら

Randomクラスにした結果、どうやら生成のコストは下がったのですが、それでもなお毎回の乱数の取得が重そうだなぁ… ということで、
試しに乱数を使うのをやめてみました*3
f:id:taichiw:20180424013553p:plain

13秒、11秒、11秒、12秒、…

ということで、10秒前後縮まりました。

乱数って用法・容量を守って正しく使わないとコワイですね。

*1:Java 乱数」でググって見つけたソースを適当にコピペしてました。白状。

*2:Listなどの感覚だとClassとInterfaceのように見えますが、どちらもClass。SecureRandomはRandomを継承したクラスです

*3:打席の結果が毎回同じなので、シミュレータとしては意味をなさなくなっています。

続・計算量を減らす:ループ内の処理を短くすることは効果大

昨日、
taichiw.hatenablog.com
なるものを書いたわけなんですが…

小細工を考える前にコードそのものの見直しをすべきかも
というお話。

結論から言うと、

一行変えただけで3分弱→30秒、5~6倍、驚きの速さにwwwww


変えたのこれだけ。
f:id:taichiw:20180422065736p:plain
github.com

従来、打席毎に乱数インスタンスを生成していた(=乱数の初期化がされていた)のをやめ、インスタンス変数にしました。
Playerクラスは1シーズン、143試合の開始時に9人分生成されているため、

1試合最低27打席 ✕ 143試合=3861回(以上) newしていたのが、 9回のみのNewに。
すると、この処理の実行時間が…
f:id:taichiw:20180421070604p:plain

バッティング毎に乱数初期化していたとき
167秒、152秒、… だったのが


Player毎に乱数初期化
29秒、30秒、26秒、29秒、… に。

どぇぇぇ。変わりすぎやろ。

「2番ペゲーロをみんなでやってみる」ために計算量と戦った話 #bpstudy

ありがたいことに、前回に引き続き今回も、Baseball Play Studyで発表させて頂く機会をいただきました。

私の発表

Twitter, 私の発表近辺
twitter.com

Twitter, 全体のまとめ
togetter.com

計算量との戦い

今回、「シミュレータ」をグルグルまわして最適な打順を探す ということを行ったのですが、計算量がネックになりました。
f:id:taichiw:20180421070604p:plain
今回のシミュレータでは、何種類かのループを回す必要がありました。

1 1シーズン、143試合分の試合を実行
2 1を、確率を収束させて精度を上げるために一定回数以上実行
3 2を、想定される打順の組み合わせ分だけ実行

さらに、同様のシミュレーションを、各球団の選手、かつ各球団ごとの打順分布に当ててみる となると、

4 3を、12球団分の選手に対して実行
5 4を、12球団それぞれの打点分布にたいして実行

が、必要となります。

ちょうど「本業」でも似たような課題を抱えており、勉強も兼ねて挑戦してみることにしました。

ループ回数を減らす

結果の精度を落とすことと引き換えに、繰り返し回数を減らすアプローチです。発表中に「ズル」と言っていたものです。

確率の収束を諦める

今回のシミュレータは確率を使っているため、施行のたびに結果が変わる、ブレが大きいものでした。
そのため、ある程度の回数実行して、結果を収束させる必要がありました。
何度か試行錯誤した結果、1000シーズン(= 143 ✕ 1000 試合)だと、年間総得点の誤差が1ー2点に収まるということがわかりました。

しかしながら、これでは数時間かかってしまう状況だったため、今回は速度優先で、100シーズンのループとしました。

施行のパターンを諦める

元々やりたかったことは、9人を並び替えた9!通りの打順をすべて試すこと。(もっというと、支配下選手70人から作られる打順をすべて試すこと)
しかし、1打順1秒としても、9!=362,880秒=丸4日間係る事になってしまってとても終わらない。
そこで、「ありえなそう」なパターンを(強めに)削ることで、55通りの組み合わせまで落としました。
f:id:taichiw:20180421070650p:plain
f:id:taichiw:20180421070729p:plain

並列化する

今回はJavaで実装していたので、Parallel Streamがある!ということで試してみました。
とても簡単に、かつ確実に速くなりました。
ただ、悩んだのが
f:id:taichiw:20180421061723p:plain:h300
このように、外のループと中のループとある場合にどこを並列化するのが良いのか。

例えば4コアCPUの場合、
中のループにParallel Streamを使うとそこだけマルチコアで処理されるため、このように断続的にCPUが100%使われるようになり
f:id:taichiw:20180421062140p:plain:w300

外のループに使うと、ほぼ常時CPUが使われるようになる(そのかわり、1巡の処理は先の場合より遅くなる)
f:id:taichiw:20180421062247p:plain:w300

そして、外と中と両方に使ってしまうと、既に外のParallel StreamでCPUが使われているため、結局外に使った場合とほぼ同じ
f:id:taichiw:20180421062247p:plain:w300

となると思います。(たぶん)

今回のようなバッチ処理の場合は、なるべく外側から並列化したほうがCPUを効率的に使え、処理時間を少しでも短くできそうだなぁ…
と思ったのですが、実際のトコロどうなんだろう。

外に投げる

最後の手段として、世の中にあるもっと高性能なコンピュータを使ってやろうじゃないかということで。
AWS LambdaがJavaも使えるということで試してみました。
qiita.com
こちらを参考にさせてもらいました。

はじめは、全処理をまとめてぽーんと投げてみたのですが

…遅い。とても遅い。

メモリ割り当てをMAXにしても、手元のPCで動かしている方がよっぽど速い。
Parallel Streamもまともに動いていないように見えました。(おそらく1プロセスに割り当てられている、仮想的なコアが1コアなんだと思います)

そこで、方針を転換。
AWS Lambdaは1000並列まで可能とのことで、それならば、多少処理が遅くても、4並列が限界な手元のPCよりはずっとマシだろう!と判断。
とにかく細かい単位で並列に投げまくることにしました。

普通のParallel Streamは可動しているマシンのコア数分しかスレッドが作られないため、この辺を参照。
qiita.com

※ただし、本当に1000並列でタスクを呼び出すためには1000スレッド作るか、Non Brockingな実装にする必要があります。今回はそこまで手を出せなかったので、12並列で終了。

プログラムをImmutableに書いておくの、めっちゃ大事

今回のように「プログラムの途中を外に投げる」ようなことをするにあたって、その箇所のコードが独立していないとなかなか厄介なことになります。
引数をメソッド内でガチャガチャ書き換えるような書き方、ダメ、絶対。

AWSを始めるなら…

この辺読んでから始めたほうがいいです。いろいろ面倒ですが…。
AWS 乗っ取り - Google 検索
従量課金コワイ。


続き書きました。
taichiw.hatenablog.com