@ledsun blog

無味の味は佳境に入らざればすなわち知れず

詰まっているのはワーカーでなくパイプ?

並列数をかえても処理時間が変わらない謎 - @ledsun blog で「サチっているかもしれない」と仮説を立てました。 それを検証するために並列数を変えて計測してみました。

並列数と処理時間のグラフ

処理時間なので低いほど性能が高いです。 3どころか1並列でサチっています。 *1

1なのは予想外です。 予想外なので、この1がヒントになりそうです。 今回の並列処理の構成上1に該当する物があります。 並列数を管理するためにワーカープールがあります。 ワーカーへのデータの受け渡しにpipeを使っています。 ソースコードは次です。

    Ractor.make_shareable(TextAlignment::CHAR_MAPPING)
    Ractor.make_shareable(TextAlignment::LCSMin::PLACEHOLDER_CHAR)
    pipe = Ractor.new do
      loop do
        Ractor.yield Ractor.receive
      end
    end

    workers = (1..4).map do
      Ractor.new pipe do |pipe|
        while msg = pipe.take
          a, ref_text, o = msg
          aligner = TextAlignment::TextAlignment.new(ref_text, o)
          m = a.map do |annotation|
            Annotation.align_annotations!(annotation, ref_text, aligner)
          end.flatten

          Ractor.yield [m, a]
        end
      end
    end

    annotations_collection_with_doc = annotations_collection_with_doc.collect do |annotations, doc|
      ref_text = doc.original_body.nil? ? doc.body : doc.original_body
      pipe.send([annotations, ref_text, options], move: true)
      doc
    end.map do |doc|
      _r, (error_messages, aligned_annotations) = Ractor.select(*workers)
      messages += error_messages
      [aligned_annotations, doc]
    end

つまりどのワーカーにデータを送るにしても、かならず1つのpipeを通ります。 pipeは1つなのです。 1は怪しい数字です。

pipeはボトルネックになり得るのでしょうか? RactorのsendはデフォルトではCopyです。

https://github.com/ruby/ruby/blob/master/doc/ractor.md#communication-between-ractors

You can choose "Copy" and "Move" by the move: keyword, Ractor#send(obj, move: true/false) and Ractor.yield(obj, move: true/false) (default is false (COPY)).

前述のソースコードのとおりオプションはつけていません。 Ractor間のデータのやりとりはCopyです。 CopyはMoveよりは遅いです。 「ワーカーがCopy待ちしている」という仮説はあり得そうです。

今回の処理では、ワーカーは受け取ったデータを変更しますが、メインRactorはワーカーの処理がおわってから、データを参照します。 Ractor間のすべてのデータの受け渡しをMoveにできるはずです。

もしかして、ついに突破口が見つかったのでしょうか?

*1:ちなみに0は並列化していない、直列処理です。比較しやすくするために0に置いてあります。