@ledsun blog

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

Ractor間のデータのやりとりでコピーを減らすには

Ractor#send move:true したときに出た3つのエラー - @ledsun blog にて、ディープコピーを減らすためにmoveオプションを使いました。 ところがエラーがでました。またエラーの内容が難解で対応出来そうにありません。 そこで、ディープコピーを減らすための別の方法を考えます。

仮説

ドキュメントを見ると次の記述があります。

ruby/ractor.md at master · ruby/ruby · GitHub

There are 3 ways to send an object as a message (1) Send a reference: Sending a shareable object, send only a reference to the object (fast)

a shareable object は参照のみをコピーです。 ソースコードでは次の箇所が該当しそうです。

https://github.com/ruby/ruby/blob/v3_1_2/ractor.c#L930-L937

    else if (rb_ractor_shareable_p(obj)) {
        basket->type = basket_type_ref;
        basket->v = obj;
    }
    else if (!RTEST(move)) {
        basket->v = ractor_copy(obj);
        basket->type = basket_type_copy;
    }

今回のRactorの構成では、次のように2回Ractor間のデータの受け渡しがあります。

main Ractor -> pipe Ractor -> worker Ractor

ディープコピーが2回走っているはずです。 これを次の振る舞いに変更します。

  1. main Ractorで送信データをshareableにする
  2. workerで受信データをディープコピーしてから編集する

これでコピー回数が減り、処理時間が短くなるはずです。

実装

   pipe = Ractor.new do
      loop do
        msg = Ractor.receive
        Ractor.yield(msg)
      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)
          copy_a = Marshal.load(Marshal.dump(a))
          m = copy_a.map do |annotation|
            Annotation.align_annotations!(annotation, ref_text, aligner)
          end.flatten

          result = [m, copy_a]
          Ractor.yield(result, move: true)
        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
      msg = [annotations, ref_text, options]
      Ractor.make_shareable(msg)
      pipe.send(msg)
      doc
    end

ポイントは次の2点です。

main Ractorで送信データをshareableにします。

Ractor.make_shareable(msg)
pipe.send(msg)

workerで受信データをディープコピーします。

copy_a = Marshal.load(Marshal.dump(a))
m = copy_a.map do |annotation|
  Annotation.align_annotations!(annotation, ref_text, aligner)
end.flatten

検証

この実装で処理時間が短くなるか確かめてみます。

コピー回数を減らして処理時間が短くなるか検証したグラフ

ディープコピーを減らした結果、処理時間は短くなりました。 期待していたよりは効果は薄く見えます。 また、詰まっているのはワーカーでなくパイプ? - @ledsun blogで掲載したグラフと特徴が異なります。 特に並列数1で処理時間が短くなっていない点が異なります。

前回の計測は、次の2点が異なります。

  1. AWSではなくローカルPCで計測している。CPUやメモリの性能が異なる。
  2. DBへのインサート処理をスキップしていない。処理時間中のRactorの動作時間は減っている。

考察

ディープコピーを減らすと処理時間が減ることがわかりました。 しかし、仮説「Ractor間のデータ受け渡し時のコピーがボトルネックになっている」までは確認できませんでした。

つぎの2つに挑戦してみます。

  1. AWSで性能計測する。ディープコピー削減の影響に関する情報を増やす
  2. 処理内容をみなおす。ディープコピーをなくす。