「手元に YARV-MJIT の実行ファイルやそのソース一式がある前提で、手元の PC でベンチマークを取りたいとしたら、さてどうするか。」 というときに、どういった手段が取れるかを探るものです。 文章を書きながら探していく想定で、まだほとんど何も探せていません。
OS やアーキテクチャに大きく依存しそうなので、汎用的な話ではなく、とりあえず手元にある x86_64 Linux のみを対象にします。
餅は餅屋、速度はプログラミングコンテスト、ということで、情報オリンピックのページに決定性向上に関する記述があったのでこれを見ていくことにします。 (なおこれは自力でたどり着いたわけではなく、twitter で他人同士の会話を眺めていたらそういう話が出ていたのでそのまま見に行っただけです)
また他に、ベンチマークの詳しい理解と修正のコツ(翻訳) というページも役に立ちそうです。 ちょっとややこしいのですが、
- Victor Stinner さんという Python のコア開発者の PyCon2017 の発表をもとに、
- Hongli Lai さんという Passenger の開発者がベンチマークの方法論を書き、
- hachi8833 さんという TechRacho の方が翻訳した
ページです。 (あとベンチマーク方法論と関係ない部分ですが、Victor Stinner さんの開発した perf は python のベンチマークフレームワークの名前であって linux の perf(1) とは別物なので注意。同じ名前なので最初混乱しました)
/etc/sysctl.d のエントリから /proc/sys/kernel/randomize_va_space に 0 をセットするべし、と書かれています。
ちょっとこれは手元の PC で実行するのは勇気がいるのでできれば遠慮したいところです。
どうしても必要であれば、ネットワークを遮断した状態で実行すれば少しは安心できるかもしれません。
しかしここで、yupo5656 さんという方の日記に ASLR(address space layout randomization) に関する記事を発見しました。 ざっと見ると実行時に setarch -RL するのでいいんじゃないかという気がします。
ただ、上の日記では PIE だと結局ランダムになっちゃうよというようなことも書かれているので、そして手元の miniruby にしろ ruby にしろ readelf -h すると
型: DYN (共有オブジェクトファイル)
と出てくるので、まだこれだけだとまずいんじゃないかと思われます。 configure.ac を見ると --disable-pie オプションがあるのでこれを試してみようかと考えています。 それで(性能や動作的に)まともな実行ファイルができるのかは試してみないとわかりません。
$(git rev-parse --show-toplevel)/configure --prefix="$RBENV_ROOT"/versions/yarv-mjit/ --disable-pie などとしてみたのですが、やっぱり「DYN (共有オブジェクトファイル)」のままでした。
よくわからないので後回しに。
もしかすると LDFLAGS に書かれた -rdynamic というフラグのせいなんじゃないかと思っているのですが、configure.ac では ELF バイナリかつ Linux なら -rdynamic をつけるというように書かれており、オプションで外したりはできなさそうに見えます。無理やり外すと今度は拡張ライブラリ .so が作成できなくなったり色々と面倒なことになりそうです。
タスクがCPU 間を移動するとコストがかかるので移動させないように、とかかれています。
ただこれ、ruby とくに YARV-MJIT では結構めんどうそうです。 というのは、まず CRuby では
- メインスレッド
- タイマースレッド
が存在します。YARV-MJIT ではそれに加えて
- ワーカースレッド
- spawn で時々出現する GCC のプロセス
が存在するわけで、4 コアなら丸々使い切ってしまいます。 ベンチマーク専用機ならいざ知らず、手元の PC で他にまったくなにも動かさないということはないはずで、
- X Window System(最近だと Wayland ?)
- シェル
- ターミナル
- Linux カーネル
- その他サービスもろもろ
なんかが動いていそうです。
操作していなければほとんど動いていないようなものかもしれませんが、といっても YARV-MJIT に完全に専有させるわけにもいかず、キャッシュの破棄や復帰やコンテキストスイッチ自体や、もろもろコストがかかるのは避けられないような気がします。
それでもタスクがまるごと移動するよりはましなはずなので、sched_setaffinity や pthread_setaffinity_np あたりを使うことにします。
なお、上に挙げたように YARV-mJIT では常時 3 スレッド + 時々 1 サブプロセスが基本なので、taskset コマンドを使うのはあまりうまくいかない気がしています。
isolcpus はどうなんでしょう、使ったことがないのでわかりませんが、これもあまりうまく使える気がしていません。
電力や発熱の問題から、turboboost やらなんやらで CPU のパフォーマンスは動的に制御されているため、これを一定にせよとのこと。 Linux での実際の設定方法は ArchLinix の Wiki の「CPU 周波数スケーリング」のページ が詳しいです。
しかし、IOI のページではパフォーマンス重視=最大出力でいけと書かれていますが、Stinner によるとむしろ最小速度にせよとのこと。 どっちがいいのでしょうね。
まずは IOI を信じて sudo cpupower frequency-set -g performance してみることにします。
と思ったら Turboboost だけは無効にしろとのこと。
echo 1 |sudo tee /sys/devices/system/cpu/intel_pstate/no_turbo してからベンチマークを取ることにします。
Java の話なので略。
/sys/kernel/mm/transparent_hugepage/enabled と /sys/kernel/mm/transparent_hugepage/defrag に "madvise" か "never" をセットし、/sys/kernel/mm/transparent_hugepage/khugepaged/defrag に 0 をセットしておけ、とのこと。
「透過的 HugePages メモリ」ってなんだ、というと、メモリページあたり 4K ではなく 2 MB を使うように自動的になる設定、ということなんでしょうか。
自動的にの部分が「透過的」ってことなんじゃないですかね。すみません適当なことを言っています。
手元だと /sys/kernel/mm/transparent_hugepage/enabled と /sys/kernel/mm/transparent_hugepage/defrag は madvise になっていたのでいいんだけすが、/sys/kernel/mm/transparent_hugepage/khugepaged/defrag だけ 1 になっていました。
echo 0 |sudo tee /sys/kernel/mm/transparent_hugepage/khugepaged/defrag することにします。
IOI のページに出てこず「ベンチマークの詳しい理解と修正のコツ」の方に出てくる話もいくつかあります。
ウォームアップしろとか回数をちゃんと設定しろというのはまあそうだし、専用のハードウェア使えというのは前提の「手元の PC で」という部分を覆すので却下。そんな予算はありません。 「マルチプロセス」は上で書いたように元々が 3 + 1 並列なのであまり使えなさそう。連続実行するというのは回数設定と同じ話と考えて良さそうです。
ということで、実際できそうなのは「カーネルスケジューラを専用のコアで実行する」だと思います。nohz_full と rcu_nocbs でできるとのこと。どちらも知らない。試してみます。
と思ったのですが、起動前に grub で設定しないといけないもののようです。うーん面倒くさい。
アフィニティについてはプログラムの改変で対応することにしたので、こんなパッチを用意しました。 これを当てた上で、以下のようにしました。
$ $(git rev-parse --show-toplevel)/configure --prefix="$RBENV_ROOT"/versions/yarv-mjit/ --disable-pie
$ grep '["optflags"]=".*-D_GNU_SOURCE' config.status || sed -i config.status -e 's/"optflags"]="[^"]*/& -D_GNU_SOURCE/'
$ ./config.status
$ make -j4 install-nodoc
$ sudo cpupower frequency-set -g performance
$ echo 1 |sudo tee /sys/devices/system/cpu/intel_pstate/no_turbo
$ echo 0 |sudo tee /sys/kernel/mm/transparent_hugepage/khugepaged/defrag
$ for i in `seq -1 1 20`; do (setarch `uname -m` -R -L ruby -j -I.ext/x86_64-linux optcarrot/bin/optcarrot --benchmark optcarrot/examples/Lan_Master.nes || exit) | if [ "$i" -gt 0 ]; then sed -ne 's/fps: //p'; else cat >/dev/null; fi; done| tee plot1.log
再起動が面倒だったので nohz_full と rcu_nocbs は試していません。
何も対策していない状態で plot2.log も作成した後、
$ (sed plot1.log -e 's/^/plot1 /'; sed plot2.log -e 's/^/plot2 /')|gnuplot -e "set xtics rotate; set yrange [20:55]; plot '<cat' using (1.0):2:(0):1 with boxplot; pause 100"
などとして箱ひげ図を書いてみました。
差がないどころか、むしろ対策後のはずの plot1 のほうが最大値・最小値の幅が大きいです。
手元の PC で、かつ見様見真似では全然効果が出ませんでした。 やっぱり素人には統計は手強いので、黙って試行回数を増やすのが無難そうです。無念。
