Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Stop using ForkJoin for web application

Stop using ForkJoin for web application

# ForkJoin ?

ここで言う ForkJoin とは、非同期タスクを以下のように処理するシステム:

- CPU コア数以下のスレッドを持ち
- スレッドごとのタスクキューを持ち
- work stealing する

具体的には [ForkJoinPool (JVM)](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ForkJoinPool.html) とか。

標準機能・ライブラリに ForkJoin 相当がない言語でも、
本稿の考え方自体は適用できることがあるだろう。
( 例えば node.js のようなイベントドリブンでも近い話はあるので )

# TL;DR

一時期の流行の影響があり騙されてしまうケースがあるが...

- ForkJoin、オメーは駄目だ (ほとんどの web アプリ用途では)
- 大抵の web アプリ用途では長所が活きない
- スレッド数が足りない
- `fairness` がないので、レイテンシの最悪値が青天井
- web アプリの並列処理では普通のスレッドプール + キューキングがベターなことが多い
- CPU コア数に関係なく単一のキューを使うのがポイント

※ web アプリ用途: 画面・API のレスポンスを返すまでの処理の中で並列処理をする用途の意

なお、CPU・メモリアクセス律速かつバッチ処理などでは ForkJoin 方式は有用 (例えば AI のミニバッチとか向いてそう、たぶん)。

saiya_moebius

February 21, 2019
Tweet

More Decks by saiya_moebius

Other Decks in Programming

Transcript

  1. Web アプリケーションで
    ForkJoin 使うのはやめてくれー
    または、web アプリケーションにおけるマルチスレッド
    処理の勘所
    Seiya Yazaki (@saiya_moebius)

    View Slide

  2. ForkJoin ?
    ここで言う ForkJoin とは、非同期タスクを以下のように処理するシステ
    ム:
    CPU コア数以下のスレッドを持ち
    スレッドごとのタスクキューを持ち
    work stealing する
    具体的には ForkJoinPool (JVM) とか。
    標準機能・ライブラリに ForkJoin 相当がない言語でも、
    本稿の考え方自体は適用できることがあるだろう。
    ( 例えば node.js のようなイベントドリブンでも近い話はあるので )

    View Slide

  3. TL;DR
    一時期の流行の影響があり騙されてしまうケースがあるが...
    ForkJoin、オメーは駄目だ (ほとんどの web アプリ用途では)
    大抵の web アプリ用途では長所が活きない
    スレッド数が足りない
    fairness
    がないので、レイテンシの最悪値が青天井
    web アプリの並列処理では普通のスレッドプール + キューキングが
    ベターなことが多い
    CPU コア数に関係なく単一のキューを使うのがポイント

    web アプリ用途: 画面・API のレスポンスを返すまでの処理の中で並列
    処理をする用途の意
    なお、CPU・メモリアクセス律速かつバッチ処理などでは ForkJoin 方式
    は有用 (例えば AI のミニバッチとか向いてそう、たぶん)。

    View Slide

  4. ForkJoin のいいところ
    (ワークロード次第で) CPU の有効活用ができるのがメリット。
    しかし、なぜそのようなメリットが得られうるのか?

    View Slide

  5. ForkJoin のアーキテクチャ
    CPU コア数かそれ以下のスレッドを持ち
    スレッドごとのタスクキューを持ち
    work stealing する
    暇なスレッドが他のスレッドのキューからタスクを奪う

    View Slide

  6. CPU から見たメモリ
    遅い。メモリへのアクセスは 100 サイクルとか掛かる。
    なので CPU はメモリアクセスをキャッシュしたりしているが、キャッシ
    ュはCPUのコアやダイごとに持っている。

    View Slide

  7. 並列処理におけるメモリ
    並列処理のタスクは親子関係・パイプライン構造になることがある: 処理
    A の出力を処理 B で使って...
    処理 A と処理 B を異なる CPU やコアで処理してしまうと、別のコアに
    通信してキャッシュをもらいにいくかメモリと通信することになり CPU
    のサイクルがすごく無駄になる。
    なので同じデータを使うタスクは同じ CPU コアで処理すると効率良い。

    View Slide

  8. ForkJoin におけるタスク割当て
    ForkJoin では、タスクが作ったタスクは同じコアのキューに入れる。
    タスクとそのタスクの子タスクは同じデータを読み書きする可能性高

    キャッシュヒット率向上

    CPU の有効活用

    実際はこれ以外にも命令キャッシュや分岐予測なども絡んでくる

    View Slide

  9. CPU コアごとにタスクのキューを持つ意味
    メモリアクセスの局所化以外にも、キューの排他制御が高速になる。
    work stealing (後述)以外では単一のコアがキューを専有するので、
    spin lock, biased lock といった手法が有効に機能し、
    キュー操作に伴うロックのオーバーヘッドが小さくなる。
    特に小さい非同期タスクを大量にさばく場合に有用な性質。

    View Slide

  10. しかし多くの web アプリではメリットが...
    DB アクセス、API コール、ログ書込み... 等の理由で I/O が発生する。
    I/O は、メモリアクセスよりも遥かに遅い。
    I/O がある限り、ForkJoin で計算効率を上げても誤差未満の効果。
    仮に CPU 利用効率が問題でも...
    ForkJoin のオーバーヘッドどころではなく重い処理が...
    HTTP プロトコルの読み書きコスト
    JSON などのパース・生成コスト
    TLS/SSL の処理コスト
    フレームワーク内部でなんか色々やるコスト
    動的メソッド呼び出し, 実行時コード生成, Proxy, ...
    Garbage Collector, 参照カウント, メモリバリア, ...
    そもそもコードがイケてなくて処理のオーダーが爆発するパターン

    View Slide

  11. ForkJoin の残念なところ: スレッド数の問題
    ForkJoin の目的は CPU コアごとの局所性を高めることで CPU を有効活
    用すること。
    しかし web アプリでは CPU のフル活用はそもそも難しい。例:
    API コールや DB へのクエリの Blocking I/O
    すべての I/O が非同期化できていればいいが...
    ログ書き出しの待ち時間
    標準出力でもファイル追記でも排他制御が発生
    性質上、ログは非同期書き出ししにくい・したくない
    スパイク的に書き込みが発生しがちなので buffer も埋まりがち
    コネクションプールの獲得などのためのロック処理
    GC による一時停止や、メモリバリアに起因するロック待ち

    View Slide

  12. ForkJoin の残念なところ: スレッド数の問題
    さまざまな理由で CPU が待ち状態になるのは無駄なので、コア数よりは
    多くのスレッドを用意したほうが CPU 資源も活用できる。
    しかしスレッド数を増やしてコンテキストスイッチすると
    コアごとの局所性は悪化するので、ForkJoin 方式のメリットがない。

    とはいえ、ForkJoin で作ってしまったシステムで緩和策としてスレッ
    ド数を引き上げるのは有効な回避策

    View Slide

  13. ForkJoin のヤバイところ: fairness
    がない
    例: ほとんどのリクエストは 0.1 [ms] で返っているのに、謎の 10 [sec]
    超えが出たりすることがあって困る
    ForkJoin 方式では fairness
    (不平等にタスクを待たせない)性質は担保
    されないため、このようなことは起き得る。

    View Slide

  14. ForkJoin のヤバイところ: fairness
    がない
    各スレッドのキューに多少のタスクが定常的に存在する場合に...
    1. 特定のスレッドが重い処理を掴む or 待ち状態になる
    2. そのスレッドのキューにあるタスクは待たされる
    3. 他のスレッドはキューにタスクがある限り work stealing しない
    問題のスレッドのタスクを他スレッドが引き取ってくれない
    4. 結果、問題のキューは進まないが、他のキューは消化される
    5. しかも問題のスレッドのキューにも新規タスクが入ってしまう
    CPU コアの局所性を fairness より重視するため
    結果、そのキューにいるタスクの待ち時間がひどいことになる
    一部のタスクだけが不平等に待たされてしまっている。

    View Slide

  15. 一般的なキューイングを用いた場合の
    fairness
    CPU・スレッドごとにキューを作るのではなく、単純に単一のキューに
    タスクを積む方式ならば fairness を確保できる。
    単一のキューを FIFO 順に処理するので、一部のタスクだけが後回しに
    なることはない。

    ミリ秒差で来たタスクが順序逆転したりする実装はあるが、しかし概
    ね fair であることがほとんど
    加えて、CPU コアの局所性にこだわらずに、余裕のあるスレッドを用意
    すれば、数本のスレッドが詰まってもシステム全体の動作は継続する。

    View Slide

  16. まとめ: 今回言及したナレッジ
    メモリキャッシュや各種のロックにアクセスに局所性があると CPU
    の有効活用にはつながる
    レイテンシの大小関係: CPU <<<
    メモリアクセス <<<<<< I/O

    View Slide

  17. まとめ: 今回言及した観点
    アプリケーション全体としてのボトルネック要因を意識しよう
    処理速度と fairness
    どちらがサービスとして重要か考えよう
    マイクロベンチマークは前者を測っていることがほとんど
    大抵の web アプリ用途でどうするべきか
    普通のスレッドプール(単一のキュー)を使いましょう
    並行実行したい数に応じた数のワーカースレッドを用意しましょう
    フレームワーク類のデフォルト値が CPU コア数になっている
    ことがあるので要注意

    AI のミニバッチのようなケースでは、CPU利用効率・スループットが
    大事なので ForkJoin がいい、という結論もありえる

    View Slide