@ledsun blog

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

インスタンス生成コストが大きかった

Ractor化する範囲を小さくしたら遅くなった - @ledsun blog で、Ractor化する範囲を小さくしたら処理が遅くなって混乱しました。 よくよくソースコードを確認したところ、変わっている場所がありました。

aligner = TextAlignment::TextAlignment.new(msg[:ref_text], msg[:options])
aligner.align(msg[:text], msg[:denotations] + msg[:blocks])

ここです。 元は次でした。

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

TextAlignment::TextAlignment.newがループの中に入っています。 このインスタンス生成が遅くなった原因のようです。 確認のために次のようにソースコードを修正しました。

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
      aligner = TextAlignment::TextAlignment.new(msg[:ref_text], msg[:options])
      results = msg[:data].map do |datum|
        begin
          aligner.align(datum[:text], datum[:denotations] + datum[:blocks])

          {
            denotations: aligner.transform_hdenotations(datum[:denotations]),
            blocks: aligner.transform_hdenotations(datum[:blocks]),
            lost_annotations: aligner.lost_annotations,
            block_alignment: aligner.lost_annotations.present? ? aligner.block_alignment : nil
          }
        rescue => e
          {
            error: e
          }
          break
        end
      end

      Ractor.yield(Ractor.make_shareable({
        index: msg[:index],
        results: results
      }), move: true)
    end
  end
end

annotations_collection_with_doc.each_with_index do |a_and_d, index|
  annotations, doc = a_and_d
  ref_text = doc&.original_body || doc.body
  results = {}

  targets = annotations.filter {|a| a[:denotations].present? || a[:blocks].present? }
  data = targets.map do |annotation|
    # align_hdenotations
    text = annotation[:text]
    denotations = annotation[:denotations] || []
    blocks = annotation[:blocks] || []

    {
      text: text,
      denotations: denotations,
      blocks: blocks
    }
  end
  pipe.send(Ractor.make_shareable({
    index: index,
    ref_text: ref_text,
    options: options,
    data:data
  }))
end.each do |annotations, doc|
  _r, results = Ractor.select(*workers)

  annotations, doc = annotations_collection_with_doc[results[:index]]
  ref_text = doc&.original_body || doc.body
  targets = annotations.filter {|a| a[:denotations].present? || a[:blocks].present? }

  messages << results[:results].map.with_index do |result, i|
    if result[:error]
      raise "[#{annotation[:sourcedb]}:#{annotation[:sourceid]}] #{result[:error].message}"
    else
      annotation = targets[i]
      annotation[:denotations] = result[:denotations]
      annotation[:blocks] = result[:blocks]
      annotation[:text] = ref_text
      annotation.delete_if{|k,v| !v.present?}

      if result[:lost_annotations].present?
        {
          sourcedb: annotation[:sourcedb],
          sourceid: annotation[:sourceid],
          body:"Alignment failed. Invalid denotations found after transformation",
          data:{
            block_alignment: result[:block_alignment],
            lost_annotations: result[:lost_annotations]
          }
        }
      else
        nil
      end
    end
  end.compact
end

計測してみます。

最新版の計測結果

比較のため非Ractor版と修正前のRactor版も載せます。

非Ractor版の計測結果

修正前のRactor版の計測結果

Ractor化する前よりは速くなりました。 修正前よりは遅いです。 まだ、なにか見落としている差分がありそうです。

とはいえ、オブジェクトのディープコピーを減らしてもめざましい効果がないことがわかりました。 「Ractor間のコピーに時間が掛かる」という説は間違っていそうです。 もしかするとRactor間でデータを受け渡すための入れ物として配列やハッシュを作っている部分がコストになっているのでしょうか?