概要
メモレベルの備忘録です。
- ErlangでTCPデータ受信系のベンチマークを流してみると、サーバのCPU使用率がやたら高いのが気になっていた
- スケジューラ使用率を見てみると、CPU使用率に比べて数分の一くらいの値だった
- CPU使用率が高いだけで、スケジューラ的には余裕がある
- 何か実際の仕事をしていた訳ではなく、スケジューラがsleepに入る前のビジーウェイトでCPUが消費されていただけの模様
- 一度スケジューラ(スレッド)がsleepに入ってしまうと、復帰までに時間が掛かり急激な負荷(タスク)の増加への反応が遅くなるので、sleepに入る前に、まずビジーウェイトで新規タスクがないかをしばらくチェックしている (注: 予想を交えて書いている)
- つまりスケジューラ使用率で見た方が、実際のErlangVMの負荷状況が正確に把握できそう
- CPU使用率だけでより正確に測定したい(ex. sar等の一般的なツールを使いたい)なら
+sbwt=none
をerlコマンドに指定するのが良さそう- スケジューラのsleep前のビジーウェイトの期間を指定できる (
none
なら完全になくなる)
- スケジューラのsleep前のビジーウェイトの期間を指定できる (
スケジューラ使用率の取得方法
『Erlang in Anger』で紹介されているreconというライブラリを使うならrecon:scheduler_usage/1
で取得可能。
> recon:scheduler_usage(1000). % 引数は測定期間(ミリ秒)
[{1,5.1613409200070315e-6}, % スケジューラ毎の使用率(max=1.0)が返される
{2,8.161533659627463e-4},
{3,1.1358267166699054e-5},
{4,1.0461382968977206e-5}]
標準関数だけを使う場合。
一回で取得可能な方法は提供されていない(おそらく)ので、若干面倒。
%% デフォルトでは情報が取得できないので、フラグをONにする
> erlang:system_flag(scheduler_wall_time, true).
%% 使用率を直接取得することはできないので、二点でスケジューラの使用状況を取得して、
%% その区間での使用率を計算する。
%% (http://www.erlang.org/doc/man/erlang.html#statistics-1 も参照)
> Start = statistics(scheduler_wall_time).
[{1,18752,156079030051}, % {SchedulerId, ActiveTime, TotalTime}
{4,4197,156079017157},
{3,11426,156079015196},
{2,4806254,156079013592}]
> End = statistics(scheduler_wall_time).
[{1,24052,171623980032},
{4,10831,171623967002},
{3,21103,171623963945},
{2,5441358,171623960966}]
> lists:map(fun({{I, A0, T0}, {I, A1, T1}}) -> {I, (A1 - A0)/(T1 - T0)} end,
lists:zip(lists:sort(End), lists:sort(Start))).
[{1,3.409467387465375e-7},
{2,4.085597620370561e-5},
{3,6.225173306295087e-7},
{4,4.267623933269757e-7}]
ちなみに以降で記載されているCPU使用率およびスケジューラ使用率は拙作のbench_utilというモジュールを使って取得されている。
TCPデータ受信での例
TCPデータ受信時に、実際にCPU使用率とスケジューラ使用率がどのような値になるのかの一例。
測定方法
TCPデータの送受信にはrecvbenchという(随分以前に書き殴りで作成した)モジュールを使用した。
サーバ側:
%% TCPサーバ側(データ受信側) - CPU: Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz (x8)
%%
%% `+sbwt`の値を変えて計測を行う: none|very_short|short|medium(デフォルト)|long|very_long
$ erl +sbwt medium -pz ebin deps/*/ebin +K true -eval 'application:start(recvbench).'
%% 3000番ポートでサーバを起動する.
%% - 受信方法アクティブモード: `{active, 32}`
%% - バッファサイズ: 1MB (デフォルト値でも良いが、大きめ(recbuf以上)の方が若干性能が向上することが多いのでついでに指定)
> recv_active_n:start_accept(3000, 32, [{sockopts, [{buffer, 1024*1024}]}]).
%% 計測: クライアント実行後に30秒間以下の関数を実行して、CPU使用率とスケジューラ使用率の平均値を採用する
> bench_util:sar([{count, 6}]).
loop time reductions context_switches gc_count gc_bytes inputs outputs run_queue cpu_util scheduler_util mem_total mem_procs mem_sys mem_atom mem_bin mem_ets ports processes
0 2015-07-22T02:48:06 675570 332677 3596 28820152 457557326 1 6 0.190736 0.058807 93221272 16828104 76393168 215345 66138280 278624 708 852
1 2015-07-22T02:48:11 685347 333213 3304 28604376 458907404 532 0 0.318427 0.078794 113394872 19021952 94372920 215345 84125640 279440 708 852
2 2015-07-22T02:48:16 683528 331951 3487 28290992 458152534 145 1 0.294529 0.072784 134849080 21448032 113401048 215345 103155456 279440 708 852
クライアント側:
%% TCPクライアント側 (データ送信側) - サーバとは別マシンで実行
%%
$ erl +sbwt medium -pz ebin deps/*/ebin +K true -eval 'application:start(recvbench).'
%% 1Mbps x 700プロセス = 700Mbpsのデータを(0.1秒毎に)送信する
> Processes = 700. % 700プロセス
> ByteRate = round(1 * 1024 * 1024 / 8). % 1Mbps (何故かバイト単位で指定する)
> RequestsPerSecond = 10. % 0.1秒毎に送信
> simple_load:start_link(ServerHost, 3000, Processes, ByteRate, RequestsPerSecond). % 起動
%% 負荷開始
> simple_load:start_load().
計測結果: sbwtオプションの値毎のCPU使用率とスケジュール使用率
+sbwt | CPU使用率 | スケジューラ使用率 |
---|---|---|
very_long | 45.48% | 4.63% |
long | 40.09% | 4.64% |
medium | 35.57% | 5.35% |
short | 29.15% | 5.77% |
very_short | 22.65% | 6.06% |
none | 14.25% | 7.32% |
ビジーウェイトの時間を短くするに従い、CPU使用率が下がって、スケジュール使用率が(若干)上がる、という傾向がはっきり表れていて面白い。
最後に
- Erlangで正確な性能測定を行いたい場合は(特にI/Oが絡む場合は?)CPU使用率だけではなく、スケジューラ使用率にも気を払った方が良さそう
- この辺りの話は『Erlang in Anger』の五章でも触れられているので、興味のある人はそちらを参照すると良いと思う