Log4j 2にも採用されたLMAX Disruptorはなぜ狂ったように速いのか?

dis

LMAXという会社はおそらくFX業者で、筆者はLMAXの開発者の講演を、InfoQの動画で何度か見たことがあった。
彼らは非常に特異な集団で、さしずめ「Javaのスピード狂」という感じだ。
印象的なのは、シングルスレッドで仕事を片付けることを強調している点だ。
「Javaならマルチスレッドで並列処理すれば性能が出ると広く思われているが、我々の仕事においてはシングルスレッドが最速だ」というような主張を何度も見た。
ゴールドマンサックスといいLMAXといい、やはり多額の金が動く会社でガチでJavaをやっている連中はカリカリにチューニングするため、技術的には非常に面白い。
彼らがコアのライブラリをOSS化してくれるというのは、金融業界を否定的な目で見る筆者からすると複雑だが、悔しいことに参考になる。

LMAX DisruptorはJavaのライブラリだ。Producer/Consumerパターンであるスレッドから別のスレッドへメッセージを渡す際のパフォーマンスを追求したものである。Log4j2のAsync実装に採用されているようだ。

Disruptorの速さの秘密はいくつかのブログで詳細に解説されているので、じっくり知りたい人はソースコードを読むとともにそちらを参照するとよいだろう。

  • ロックを極力排除し、Producerからのアクセスの一ヶ所でしか使用していない
  • データ構造としてリングバッファを使い、メッセージが格納されている領域のインデックスを各Consumer等がシーケンス番号として管理することで、ロックを減らしている

何とも、非常に頭が良い解決方法である。

シーケンス番号はどんどん上がっていくので、配列を用意すると無限の大きさの配列が必要となってしまう。しかしリングバッファをぐるぐる廻して、全Consumerが処理済みの領域を上書きして使っていくことでこの問題を解決している。

どんなConsumerが存在するのか?が中央で管理されているのが面白い。オブジェクト指向的にはイケていないが、ロックを排除するための工夫ということだろう。

2015/01/06追記
最後の「オブジェクト指向的にはイケていないが…」の部分に対して「別にイケていなくてもいいのでは」的な反応を頂いたので追記しておく。

まさしくその通りで、Disruptorはパフォーマンスを極めるためのものなので、「オブジェクト指向的にイケて」いなくてもOKである。
しかし実は筆者ではなく、LMAXの中の人が、この点について(かなり強く)言及しており、ここは大きなポイントだ。
オブジェクト指向的に綺麗に設計する(関心を分離する)ことにこだわることがパフォーマンス劣化(正確にはcontention)につながっている
と、http://lmax-exchange.github.io/disruptor/files/Disruptor-1.0.pdfに記述されている。

Further investigation and a focus on the computer science made us realise that the conflation of concerns inherent in
conventional approaches, (e.g. queues and processing nodes) leads to contention in multi-threaded implementations,
suggesting that there may be a better approach.

同様にこちらのブログの以下の部分も同様。

The ConsumerTrackingProducerBarrier has a list of all the Consumers that are accessing the ring buffer. Now to me this seemed a bit odd – I wouldn’t expect the ProducerBarrier to know anything about the consuming side. But wait, there is a reason. Because we don’t want the “conflation of concerns” a queue has (it has to track the head and tail which are sometimes the same point), our consumers are responsible for knowing which sequence number they’re up to, not the ring buffer. So, if we want to make sure we don’t wrap the buffer, we need to check where the consumers have got to.

筆者の書き方が悪かったのだが、つまり「オブジェクト指向的にきれいな設計を捨てることで性能向上を実現している」という、トレードオフの部分が非常に面白いのだ。(そのため「オブジェクト指向的にはイケていない」と書いたことは文脈的におかしくないし、むしろもっと強調してもよい部分だと考えている。)

ちなみに上記の指摘をされた方からは「アルゴリズム的な話に、オブジェクト指向をもってくるのがナンセンスなんです。」とも言われたのだが、上記PDFの図を見てもらえばわかるようにDisruptorは典型的なオブジェクト指向で書かれたソフトウェアであるので、Disruptorについて言及している本エントリにおいて「オブジェクト指向をもってくるのがナンセンス」と言われても困る。おそらく指摘された方はDisruptorのドキュメントやソース等は読まずに、本エントリだけに脊髄反射されただけだろうと思う。

Disruptorはいわば「守破離」的に、全体としてはオブジェクト指向に従いながら、オブジェクト指向で当たり前とされる「関心の分離」の一部だけをわざと崩すことで最高のパフォーマンスを実現しているところが素晴らしい。

Leave a comment