LoginSignup
12
3

More than 1 year has passed since last update.

Erlang VMのprocess_flagのmessage_queue_dataのon_heapとoff_heapで軽く性能評価してみました

Last updated at Posted at 2021-12-08

はじめに

BEAM/OTP対話#9The Erlang Runtime System3.2節から3.6節まで読んで知ったのですが,process_flagmessage_queue_dataon_heap(デフォルト)にするかoff_heapにするかで,メッセージを受け取るときの挙動と性能が変わるという話を聞いて,とても興味を持ったので実験をしてみました。

また,bencheeを使って入力データの種類ごとにベンチマークを集計する方法,ベンチマークごとに前処理する方法についてもこの記事で紹介しています。

この記事はNervesJP Advent Calendar 2021の9日目の記事です。8日目は @torifukukaiou さんのNerves meets SORACOM (Elixir)でした。

BEAM/OTP対話#9で読んだところ

The Erlang Runtime System

この部分は,Erlang VMの内部に迫る内容で,メッセージパッシングを性能向上させるために,いかに安全性を保ったままロック獲得によるオーバーヘッドをなくしていくか,という話で,とても勉強になりました。BEAM/OTP対話#9動画を公開していますので,ぜひ見てみてください。Erlang VMは,大袈裟ですが,人類が誇るべき財産だと思いました。

ちなみにThe Erlang Runtime Systemの著者Erik Stenmanさんに連絡して,日本語訳をすることの許諾を得ました。近々取り組もうと思っています。翻訳チームに入りたい方,ご連絡ください。

on_heapとoff_heapで何が変わるか?

on_heapはOTP 19以前からあるようなメッセージ受信の方法です。初期状態ではon_heapとなります。条件が整えば,ヒープに直接メッセージを書き込みますが,他のプロセスと競合しているなど,条件が整わない場合には m-buf というメモリ領域を別途確保してまとめてメッセージを書き込み,「あとで読んでね」と受信する側のプロセスに教えます。条件が整ったときにはコピーが発生しないので,高速になります。そのため1つのプロセスからしか受信をしない場合には有効です。反面,たくさんのプロセスから同時に受信するような場合には性能が落ちるとのことです。

これに対し,off_heapはOTP 19以降の新しいメッセージ受信の方法です。この戦略だと,状況に関係なく,m-bufというメモリ領域を別途確保してまとめてメッセージを書き込むことにします。多数のプロセスから受信をする場合に有効だとされています。

Elixirでon_heapとoff_heapを切り替えるには?

Erlang VM全体で off_heap に切り替えるには次のようにします。

elixir --erl "+hmqd off_heap" *.{ex,exs}

なお,*.{ex,exs}には実行させたいElixirのファイル名が入ります。

Mixを使って Erlang VM 全体を off_heap に切り替える場合には次のようにします。

elixir --erl "+hmqd off_heap" -S mix

このようにしてErlang VM全体をoff_heapにしてみて,パフォーマンスが向上するかどうかを確認します。向上しないならば,パフォーマンスボトルネックは他に原因があります。向上するならば,次にボトルネックになっているプロセスを特定してそのプロセスだけを off_heap にします。ボトルネックを見るには,プロセスごとのメッセージの滞留度合いを見れば良いかと思います。BEAM/OTP対話#8にて解説しました。BEAM/OTP対話#8の動画を公開していますので,是非参照ください。

特定のプロセスをoff_heapにするには,そのプロセスの中で次の式を実行します。

Process.flag(:message_queue_data, :off_heap)

GenServerを使っているときには,initの中でこの式を実行すると良いでしょう。

実験

次のようなコードで実験をしてみました。プログラミングElixir第2版にも書かれている Pmap (Parallel map)の例です。

defmodule Pmap do
  @moduledoc """
  Documentation for `Pmap`.
  """

  def pmap(collection, func) do
    collection
    |> Enum.map(&Task.async(fn -> func.(&1) end))
    |> Enum.map(&Task.await/1)
  end
end

bencheeを使って次のようなベンチマークコードを書いてみます。

map_fun = fn i -> [i, i * i] end

Benchee.run(
  %{
    "pmap (on_heap)" => {
      fn input -> Pmap.pmap(input, map_fun) end,
      before_scenario: fn input ->
        Process.flag(:message_queue_data, :on_heap)
        input
      end
    },
    "pmap (off_heap)" => {
      fn input -> Pmap.pmap(input, map_fun) end,
      before_scenario: fn input ->
        Process.flag(:message_queue_data, :off_heap)
        input
      end
    }
  },
  inputs: %{
    "Small" => Enum.to_list(1..1_000),
    "Medium" => Enum.to_list(1..10_000),
    "Bigger" => Enum.to_list(1..100_000)
  },
  memory_time: 2
)
  • 入力データ input は次の3種類です。
    • Small(1,000個の要素からなるリスト)
    • Medium(10,000個の要素からなるリスト)
    • Bigger(100,000個の要素からなるリスト)
  • それぞれの入力データに対し,on_heapoff_heapのそれぞれの場合で性能がどのように変わるのかを検証します。
    • この切り替えは before_sceario: fn -> ... end を使って,ベンチマーク実行の直前で実行するようにしてみました。

実験環境

次のような環境で実験しました。

Operating System: macOS
CPU Information: Apple M1
Number of Available Cores: 8
Available memory: 16 GB
Elixir 1.13.0
Erlang 24.1.7

実験結果

得られたベンチマーク結果は次のとおりでした。

##### With input Bigger #####
Name                      ips        average  deviation         median         99th %
pmap (off_heap)          2.37      421.29 ms     ±1.84%      418.69 ms      437.96 ms
pmap (on_heap)           2.31      432.39 ms     ±2.12%      431.32 ms      454.44 ms

Comparison: 
pmap (off_heap)          2.37
pmap (on_heap)           2.31 - 1.03x slower +11.10 ms

Memory usage statistics:

Name                    average  deviation         median         99th %
pmap (off_heap)        66.38 MB     ±1.58%       66.92 MB       67.05 MB
pmap (on_heap)         66.93 MB     ±0.38%       66.99 MB       67.16 MB

Comparison: 
pmap (off_heap)        66.92 MB
pmap (on_heap)         66.93 MB - 1.01x memory usage +0.54 MB

##### With input Medium #####
Name                      ips        average  deviation         median         99th %
pmap (off_heap)         23.98       41.70 ms     ±3.02%       41.56 ms       44.82 ms
pmap (on_heap)          23.85       41.93 ms     ±3.77%       42.02 ms       46.16 ms

Comparison: 
pmap (off_heap)         23.98
pmap (on_heap)          23.85 - 1.01x slower +0.23 ms

Memory usage statistics:

Name                    average  deviation         median         99th %
pmap (off_heap)         6.61 MB     ±1.32%        6.63 MB        6.70 MB
pmap (on_heap)          6.57 MB     ±1.82%        6.59 MB        6.69 MB

Comparison: 
pmap (off_heap)         6.63 MB
pmap (on_heap)          6.57 MB - 0.99x memory usage -0.03901 MB

##### With input Small #####
Name                      ips        average  deviation         median         99th %
pmap (off_heap)        247.87        4.03 ms     ±7.31%        4.04 ms        4.76 ms
pmap (on_heap)         235.61        4.24 ms     ±7.01%        4.26 ms        5.08 ms

Comparison: 
pmap (off_heap)        247.87
pmap (on_heap)         235.61 - 1.05x slower +0.21 ms

Memory usage statistics:

Name                    average  deviation         median         99th %
pmap (off_heap)       650.49 KB     ±2.00%      653.55 KB      670.22 KB
pmap (on_heap)        652.15 KB     ±1.88%      654.64 KB      672.29 KB

Comparison: 
pmap (off_heap)       653.55 KB
pmap (on_heap)        652.15 KB - 1.00x memory usage +1.66 KB

off_heapの方がいずれも速くなっていますが,若干ぐらいかな,というところです。

考察

今回の実験で用いたプログラムだと,送信側のプロセスの数がとても多いものの,各プロセスの処理時間が短く,かつ送信が1回限りですので,プロセス間の競合が起きにくい状況だと思います。したがって,off_heapによる性能向上の効果があまり無かったものと思います。

逆に言えば,もし,各プロセスが長い間にわたって生存し,送信をたびたび行うようなものであれば,off_heapの効果は大きくなるんじゃないかと思います。今後,実験していきたいと思います。

おわりに

そういうわけで,ひとまず Elixir において,Erlang VM全体と特定のプロセスそれぞれについて,OTP 19以降でサポートされた off_heap に設定する方法がわかったので,満足とします。また,ついでにbencheeを使って入力データの種類ごとにベンチマークを集計する方法,ベンチマークごとに前処理する方法がわかったこともよかったです。

明日は @the_haigo さんのNerves で GPS Loggerを作ってみたです。お楽しみに。

12
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
3