TCPデータ受信時のCPU使用率が高すぎると思ったら、スケジューラ使用率も見てみると良いかもしれない

概要
====

メモレベルの備忘録です。

- ErlangでTCPデータ受信系のベンチマークを流してみると、サーバのCPU使用率がやたら高いのが気になっていた
- スケジューラ使用率を見てみると、CPU使用率に比べて数分の一くらいの値だった
  - CPU使用率が高いだけで、スケジューラ的には余裕がある
  - 何か実際の仕事をしていた訳ではなく、スケジューラがsleepに入る前のビジーウェイトでCPUが消費されていただけの模様
     - 一度スケジューラ(スレッド)がsleepに入ってしまうと、復帰までに時間が掛かり急激な負荷(タスク)の増加への反応が遅くなるので、sleepに入る前に、まずビジーウェイトで新規タスクがないかをしばらくチェックしている (注: 予想を交えて書いている)
  - つまりスケジューラ使用率で見た方が、実際のErlangVMの負荷状況が正確に把握できそう
- CPU使用率だけでより正確に測定したい(ex. sar等の一般的なツールを使いたい)なら`+sbwt=none`をerlコマンドに指定するのが良さそう
  - スケジューラのsleep前のビジーウェイトの期間を指定できる (`none`なら完全になくなる)

スケジューラ使用率の取得方法
=========================

[『Erlang in Anger』](http://www.erlang-in-anger.com/)で紹介されている[recon](https://github.com/ferd/recon)というライブラリを使うなら`recon:scheduler_usage/1`で取得可能。

```erlang
> recon:scheduler_usage(1000). % 引数は測定期間(ミリ秒)
[{1,5.1613409200070315e-6}, % スケジューラ毎の使用率(max=1.0)が返される
 {2,8.161533659627463e-4},
 {3,1.1358267166699054e-5},
 {4,1.0461382968977206e-5}]
```

標準関数だけを使う場合。
一回で取得可能な方法は提供されていない(おそらく)ので、若干面倒。

```erlang
%% デフォルトでは情報が取得できないので、フラグを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](
https://github.com/sile/bench_util)というモジュールを使って取得されている。

TCPデータ受信での例
=================

TCPデータ受信時に、実際にCPU使用率とスケジューラ使用率がどのような値になるのかの一例。

測定方法
--------

TCPデータの送受信には[recvbench](https://github.com/sile/recvbench)という(随分以前に書き殴りで作成した)モジュールを使用した。

サーバ側:

```erlang
%% 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
```

クライアント側:

```erlang
%% 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』](http://www.erlang-in-anger.com/)の五章でも触れられているので、興味のある人はそちらを参照すると良いと思う