RubyのThread::SizedQueueのチューニング

前提

  • Thread::Queueは際限なくサイズが伸びるので使わない。Thread::SizedQueueを使う。
  • 生産者(Thread::SizedQueue#pushする)側は1スレッドとする。
  • 消費者(Thread::SizedQueue#popする)側は複数スレッドとする。

結論

いろいろい試してみたが、次のようにすればよさそう。

消費者(popする)側が高速に処理できるとき

スループットを最大化するためには、Thread::SizedQueueのサイズを目安として消費者側のスレッド数の10倍程度に設定する。

消費者(popする)側が低速にしか処理できないとき

Thread::SizedQueueのサイズをいくら大きくしたところで無駄に滞留するだけなので、なるべく小さなサイズにする。極論としてはサイズ1でもかまわない。

理由

Rubyの条件変数(Thread::ConditionVariable)は、waitメソッドで同時に待ち状態になるスレッドの数が多くなるほど加速度的に遅くなるようだ。signalメソッドで通知先を1スレッドに限定しても速度低下は緩和されない。 Thread::SizedQueueは組み込みライブラリなので厳密には違うのだが、スレッド待機の実装はThread::ConditionVariableとそんなに変わらないと思われる。

速度低下を避けるためにはwaitの呼び出しをなるべく減らさなければならず、Thread::SizedQueueが空だとpopするときにwaitするのでキューにデータが入っている状態を保たなければならず、そのためにはThread::SizedQueueのサイズをpopするスレッド数より十分大きくする必要がある。

ちなみにpushする側でもwaitは発生するが、1スレッドに限定されるなら速度低下は気にするほどではない。