とあるRuby on RailsアプリケーションのSidekiqで動くバックグラウンドジョブを高速化をしています。 CPUバウンドな処理に時間が掛かっています。 並列化して高速化できるか試しています。 RactorでWoker pool - @ledsun blog で、4コアのPCで並列化できるところまで確認できています。 ただし、4並列というほどには高速化出来ていませんでした。 検証につかっているPCやWSLなどの環境に起因するものでしょうか? 検証用にAWS EC2を借りて環境構築しました。
8コアあるt3.x2largeを使います。 今回は使って4並列と8並列で計測して、高速化できるか試します。
4並列で計測
つぎのようにRactorを使って4ワーカーで実行するソースコードがあります。 https://github.com/pubannotation/pubannotation/blob/54a53d80d39a521eff3f4be4110b844f075f4d26/app/models/project.rb#L760-L772
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
計測してみましょう。
8並列で計測
これに次のパッチをあてて8並列します。
diff --git a/app/models/project.rb b/app/models/project.rb index 9a769ef2..85da2cac 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -757,7 +757,7 @@ class Project < ActiveRecord::Base end end - workers = (1..4).map do + workers = (1..8).map do Ractor.new pipe do |pipe| while msg = pipe.take a, ref_text, o = msg @@ -826,7 +826,7 @@ class Project < ActiveRecord::Base messages << { body: "Uploading for #{num_skipped} documents were skipped due to existing annotations." } if num_skipped > 0 - InstantiateAndSaveAnnotationsCollection.call(self, aligned_collection) if aligned_collection.present? + #InstantiateAndSaveAnnotationsCollection.call(self, aligned_collection) if aligned_collection.present? messages end
パッチの後半はDBへのインサート処理をコメントアウトしています。 これは4並列のときも行っています。
計測してみます。
並列数をあげたのに処理時間は変わりません。
そもそも並列化できていないのでしょうか?
直列で計測
並列化しないと次のようなソースコードです。
annotations_collection_with_doc.each do |annotations, doc| ref_text = doc.original_body.nil? ? doc.body : doc.original_body aligner = TextAlignment::TextAlignment.new(ref_text, options) messages += annotations.map do |annotation| Annotation.align_annotations!(annotation, ref_text, aligner) end.flatten end
これで計測してみましょう。
一分近く処理時間が伸びます。 並列化できてそうです。
考察
8並列と4並列の処理時間が全く一緒なのが予想外の結果です。 2倍にならないとしても、いくらか高速化することを予想していました。 これはどのような現象が起きているのでしょうか?いくつか仮説を考えてみます。
「並列化した先で実は同一の資源を使っている部分がある。」 今回Ractorで並列化しているので、メモリやDBへの接続を共有したらエラーがおきるはずです。
「並列処理が終わったあとにボトルネックがある。」 stackprofで実際に掛かっている時間を計測すれば、起きているかどうか確認できそうです。 Stackprofで時間の掛かる処理を探そうとして上手く行かなかった話 - @ledsun blog で、直列処理を計測したときは、並列化した部分に時間が掛かっていました。 この線もなさそうです。
「8並列の遙か手前、たとえば3並列でサチっている。」 原因は推測できませんが、起きているかもしれません。 これは2並列、3並列で計測すれば、起きているかどうか確認できそうです。