ElixirのFlowを用いると,極めて簡潔に並列処理を書くことができます.特にI/Oバウンドの場合,威力を発揮します.一方,CPUバウンドの場合には,負荷の軽い処理では逐次処理よりかえって遅くなるため,簡潔かつ有効な事例を示す必要がありました.そこで,本記事では,:crypto
とBase
を用いて比較的重たい暗号処理を行わせたFlowによる並列処理を紹介し,有効性を示します.
逐次処理版のコード
Elixirではパイプライン演算子を用いることで,上から下へ流れるように読むことができるプログラムコードを記述できるという点が,大きな強みとなっています.
以下に1,000,000個の1000バイトの強固な暗号鍵を生成してBase32でエンコードし,結果として1,000,000個の1600文字の暗号文字列を得るという,Elixirコードを紹介します.
1..1_000_000
|> Enum.map(fn _ -> :crypto.strong_rand_bytes(1000) end)
|> Enum.map(& Base.encode32(&1, case: :lower)) end)
なお,このコードを作る際に参考にした記事は次のとおりです.
並列処理版のコード
ではFlowによる並列処理にするとどうなるでしょうか.
1..1_000_000
|> Flow.from_enumerable()
|> Flow.map(fn _ -> :crypto.strong_rand_bytes(1000) end)
|> Flow.map(& Base.encode32(&1, case: :lower)) end)
|> Enum.to_list()
なお,Flowを用いるには,Mix.install([:flow])
をあらかじめ実行するか,Mixプロジェクト中のmix.exs
にFlowを読み込む設定をします.
Flowを用いた時の注意点としては,並列処理をした時の到着順にリストが生成されるので,結果の順番がランダムになることです.この例では,各要素の計算結果は要素の順番に全く依存しないランダムなものなので,特に問題になることはありません.もし到着順ではなく,最初に生成したリストと同じ順番にしたい時には,適宜ソートをする必要があります.
実行時間の計測
Bencheeというベンチマークプログラムを使って,逐次版と並列版の実行時間を計測・比較します.
Mix.install([:flow, :benchee])
Benchee.run(
%{
"sequential execution" => fn -> 1..1_000_000 |> Enum.map(fn _ -> :crypto.strong_rand_bytes(1000) end) |> Enum.map(& Base.encode32(&1, case: :lower)) end,
"parallel execution" => fn -> 1..1_000_000 |> Flow.from_enumerable() |> Flow.map(fn _ -> :crypto.strong_rand_bytes(1000) end) |> Flow.map(& Base.encode32(&1, case: :lower)) |> Enum.to_list() end
}
)
Operating System: macOS
CPU Information: Apple M2 Max
Number of Available Cores: 12
Available memory: 96 GB
Elixir 1.15.7
Erlang 26.1
Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 0 ns
reduction time: 0 ns
parallel: 1
inputs: none specified
Estimated total run time: 14 s
Benchmarking parallel execution ...
Benchmarking sequential execution ...
Name ips average deviation median 99th %
parallel execution 0.71 1.40 s ±3.20% 1.40 s 1.46 s
sequential execution 0.180 5.54 s ±0.00% 5.54 s 5.54 s
Comparison:
parallel execution 0.71
sequential execution 0.180 - 3.96x slower +4.14 s
並列実行版(parallel execution)の方が逐次実行版(sequential execution)より3.96倍高速です.