もう1月も前ですが、5月18日にJJUG CCC 2019 Springが開催されました。Oracle Codeの次の日ですね。
今回はProject LoomのFiberについてプレゼンしました。資料はこちら。
Fiberはいわゆる軽量スレッドです。
プレゼンではなぜ軽量スレッドが必要なのかという部分について、かなり時間をかけて説明しました。
OSに強く結びついたThreadを使った場合、コンテキストスイッチにコストがかかります。時間もかかるし、メモリも多く使用します。
これは、スレッドごとに用意されるJVM Stackをすべて退避させたり、もどしたりする必要があるからです。JVM Stackは、そのスレッドでコールされているメソッドコールのスタックで、メソッドで使われるローカル変数やオペランドスタックも含みます。
また、OSがスレッドのスケジューリングを管理しているため、コンテキストスイッチがいつ行われているかもJava側からは管理することができません。
そのために求められているのが、JVMで管理する軽量スレッド、つまりFiberというわけです。
これに対し、Fiberを使えば、ワーカースレッド上で動作するため、スケジューリングはJVMで管理でき、状態の退避用のメモリも少なくてすみます。
Fiberでは、スケジューリングには既存のFork/Join Frameworkが使用されます。コンテキストスイッチには処理の中断、再開のための仕組みが必要ですが、これはContinuationで行います。Continuationはいわゆる継続を実現させるための仕組みですが、JavaのContinuationは限定継続になります。
プレゼンの中ではContinuationをかなり強引にwait-notifyAllと同じようなものと説明してしまったため、誤解させてしまったのではないかと反省しています。もちろん、Continuationもwait-notifyAllも処理の中断・再開をするという機能はありますが、一般的なContinuationでできることは処理の中断・再開だけではありません。ちょっと説明不足でした。
しかし、今のところProject LoomではContinationを積極的に活用するようなシナリオはないように見えます。現状は、I/O待ちのためにパフォーマンスが落ちていることに対して、Fiberを使ってI/O待ちをなるべく解消することがメインの目的のようです。
これは、処理の中断・再開を行うFiberのparkメソッド、unparkメソッドがデフォルトのアクセス制御であることからも分かります。park/unparkを使用しているのは、ReentrantLockクラスなどのロック系のクラスや、java.nio.channelパッケージのソケット通信などのクラスなどです。
プレゼンの中では前日に行ったOracle Codeのデモについても説明しました。JDKに含まれているHTTPサーバーであるcom.sun.net.httpserver.HttpSErverクラスを使用して、Fiberを指定しているだけです。これだけで、既存のExecutorServiceインタフェースを使用した場合よりもスレッド数やスループットが向上しました。
とはいうものの、現状Fiberはそこまで速くありません。
コンテキストスイッチが起きないようにうまくタスク分けして、I/O待ちも多重化するなどして待たなければいけないスレッドを最小化するなどのチューニングを行えば、Fiberより高いパフォーマンスを得ることができます。
ただ、それをするには設計やチューニングなどの高度な知識や経験が求められます。Fiberを使うことで誰でも簡単にパフォーマンスを向上させられるということが、Fiberの意義の1つなのではないかと感じています。
また、Fiberのさらなるパフォーマンス向上のためにJVM Stackを操作することも考えられているようなので、今後に期待したいですね。
さて、以下は会場やsli.doでの質問とその回答です。
Q Fiberの中断前と再開後でワークスレッドが変わることがあるのか?
A. あります。
キターーーーーーー 「この分野は素人なのですが」な質問!!!
なんとか答えられてよかったですw
さて、現状はFiberはシリアライザブルではないのですが、シリアライザブルにする計画があります。このため、ワークスレッドが変化することもありますし、処理途中のFiberを他のCPUに移動させてそこで再開というシナリオも考えられます。
質問した伊藤さんも心配されていますが、ロガーのようにスタックトレースを保持させるようなものは、Fiberにするとやばいかもしれません。
Q. ある時点の処理で中断していたものを複数回再実行することはできるか?
A. 一般的な継続だとこれができるのですが、今のところJavaのFiberでは計画されていないようです。
Fiberは継続を行うための状態管理に既存のJVM Stackをそのまま使っています。そのため、再実行を行うには、JVM Stackを操作するバイトコードが必要になるはずですが、そこまでやるとかなり大がかりになってしまうためかもしれません。
Q. ワークスレッドは自動的に用意されるのか?
A. されます。
デフォルトではForkJoinPoolを利用してワークスレッドの割り当てを行っています。もちろん、他のスレッドプールに置き換えることもできます。
Q. OSによらないThreadがFiberだとしたら、Green Threadのようなもの?
A. まさにGreen Threadです。
Javaの初期のころ、Solaris向けのJavaはネイティブのスレッドではなく、JVMが管理するスレッドを使用していたのですが、それがGreen Threadです。
Q. Kotlinのcoroutineと何が違うのか?
A. Kotlinのことをよく知らないのですが、同じような機能のようですね。
Q. Continuationのscopeがよく分からない。コンテキストスイッチを行いたい複数のFiberからなるグループのようなもの?
A. 継続処理を行いたい範囲をしめすものです。
どこからでも自由に中断・再開を行うのは難しいので、継続ができる範囲を決めているという感じです。Fiberも内部でスコープを持っていて、Fiberのタスク処理の中だけで継続を行っています。
Q. ThreadとFiberの使い分け指針が知りたい
A. I/Oの待機やロックの取得を含む非同期処理であればFiberを使うのがよいと思います。
計算処理だけであるならパラレルストリームやFork/Join Frameworkを使いましょう。それ以外だったらThreadになると思いますが、今でもExecutorServiceにタスクを渡すのが主で、Threadを直接使うことはまずないはずです。
Q. 既存のThreadをFiberに置き換えるイメージがつかめません。ExecutorServiceでThreadを使うようなことはFiberでもできるのでしょうか。
A. Fiber.scheduleメソッドがExecutorService.submitメソッドのような感じです。
Fiber.scheduleメソッドはstaticメソッドなので、ExecutorServiceのようにExecutorsクラスでExecutorServiceインスタンスを生成して、それからsubmitするまでを行っているような感じです。
Q. Loom入りのJDKを含んだDocker Imageは公開されているか?
A. 私が調べたときにはなかったです。
Docker上でLoomのJDKをビルドしようとするとなぜか落ちてしまうので、私はローカルな環境でビルドしてからそれをコピーするDockerfileを作ってDockerのイメージを作ってました。
Q. Loomのリリース予定は未定だとしても、現時点での目標とかはあるのでしょうか?
A. 明確なリリース予定時期がFiberを作っている人たちの間ではあるのかもしれませんが、私には分からないです。
少なくとも、Windowsで動作できるようにならないとリリースはできないので、そこが最低限の目標となるのではないでしょうか。