Elixirで速度を追い求めるときのプログラミングスタイル PartⅡ,Pelemayの近況もあるよ


この記事はfukuoka.ex Elixir/Phoenix Advent Calendar 2020 25日目の記事です。

昨日は @Yoosuke さんの「高校生に教えていた Elixirのカリキュラムの一部をクリスマスイブに公開します。(5時間分まで)」でした。

fukuoka.ex Elixir/Phoenix Advent Calendar 2020は,WebテクノロジーのLGTM数順で堂々1位となりました。 素晴らしいことです!

さて,今日は「Elixirで速度を追い求めるときのプログラミングスタイル」の第2弾ということで,「Elixirで速度を追い求めるときのプログラミングスタイル PartⅡ,Pelemayの近況もあるよ」をお送りします。

BasicBenchmarkElixir

今回のベンチマークのコードはこちらです。

検証環境

> elixir -v
Erlang/OTP 23 [erts-11.1.1] [source] [64-bit] [smp:36:36] [ds:36:36:10] [async-threads:1] [hipe]

Elixir 1.11.2 (compiled with Erlang/OTP 23)

iMac Pro 2017 のCPU/GPU全部盛りで検証しています。

結果

## EnumBench
benchmark name iterations   average time 
Enum.map               50   55946.10 µs/op
recursive              50   56215.64 µs/op
## ListAddBench
benchmark name iterations   average time 
add long list  1000000000   0.01 µs/op
add head       1000000000   0.01 µs/op
add tail       1000000000   0.01 µs/op
## ListPopBench
benchmark name iterations   average time 
pop head        100000000   0.02 µs/op
pop tail               50   37582.68 µs/op
## ListReverseBench
benchmark name iterations   average time 
reverse               200   9471.07 µs/op
## ProcessBench
benchmark name iterations   average time 
echo              1000000   1.42 µs/op
send list         1000000   1.42 µs/op
  • EnumBenchEnum.maprecursiveの実行時間を比較しています。ご覧の通り,ほぼ差はなく,Enum.mapを選ばない手はないかなと思います。
  • ListAddBenchは次の3つのベンチマークです。どれも速度に差はなく,極めて高速に処理できることがわかります。
    • add long listは長いリスト2つを++で結合します。
    • add head は先頭に1つ要素を追加します [:a | @list]
    • add tail は末尾に1つ要素を追加します @list ++ [:a]
  • ListPopBenchは次の2つのベンチマークです。先頭から要素を取り出すのは極めて高速ですが,末尾の要素を無理矢理取り出すのは極めて低速です。
    • pop head[head | tail]を実行します。
    • pop tailList.delete_at(@list, -1)を実行します。
  • ListReverseBenchEnum.reverseを計測しています。リスト長が長くなると実行時間がかかりますが,他の操作に比べると比較的高速です。
  • ProcessBenchは,spawnsendreceiveの一連の操作にかかる時間を計測しています。プロセスを起動しているとは思えないくらい,極めて高速に実行できます。ちなみにプロセスをプールするよりも高速に実行できることがわかっています。送るメッセージの長さを変えても実行時間は変わりませんでした。ノードを起動して,分散実行した時を検証してみたいところです。

所感

次の結果は,今までの常識を塗り替える物だったので,特に驚きました。

  • ++は結合するリストの長さによらず,極めて高速に実行できること
  • Enum.mapと再帰スタイルの実行時間に差がないこと
  • プロセスの起動は極めて高速で,1〜数μ秒オーダーであること,プールするよりも速いこと

ElixirおよびErlangのアップデートにより,これらの改善が図られてきたのではないかと思うと,胸熱です。

この結果を今後どう活かすか?

なぜこのような基礎的なベンチマークを取っていたかというと,Pelemayのマルチコア並列処理版を作ろうとしているからです。プロトタイプ版はすでに動いていて,従来のFlowやPmapはもちろんのこと,EnumやPelemayよりも高速に動作できることを確かめています。数々の試作を重ねて基礎データを積み重ねてきたことで,設計・実装する上での肝となる部分が把握できたので,現在,プロトタイプ第2弾を実装しているところです。

この成果については,論文発表をしてから改めて発表する予定です。

本研究成果は、科学技術振興機構研究成果展開事業研究成果最適展開支援プログラム A-STEP トライアウト JPMJTM20H1 の支援を受けた。