作ったもの : profile.d
はじめに
D言語には標準でプロファイリング機能が含まれている。DUBプロジェクトであれば、以下のように実行することで trace.log
というプロファイル結果を得ることができる。
dub run --build=profile
各関数の最初と最後にタイマーを仕込む形でプロファイリングが行われており、各関数がいつ呼ばれいつ終了したか、何回呼ばれたかなどのメトリクスを取ることができる。 d-prifile-viewer を使うことでこれを可視化できる。
コンパイラに含まれている機能であり気軽に試せるというメリットはありつつも、以下の問題があるため、より良いプロファイリング方法が必要だろう。
- オーバーヘッドが大きい
- タイマーを仕込む影響
- コールスタックを得ることができない
- 関数がどこから呼ばれているかがわからない
- Linux では d-profile-viewer を使えない
- 私は Linux で生きている
そこで本稿では Linux での性能解析でよく使われる perf
によるプロファイリング方法を解説する。
perf
前提
以下の環境で動作確認を行っている。
- Linux Mint 20 ulyana
- AMD Ryzen 5 3600
Ubuntu でも Intel でも変わらないはず。
perf とは
ググってくれ。高速化やるか、となった時はまずはだいたいこれを使う。
準備
このあたりはD言語に限らない、一般的な話。
1. perf インストール
Linux のツールとして提供されている。apt からインストールする。
sudo apt install linux-tools-generic
私の環境ではこれで問題なかった。場合によっては以下のようにカーネルバージョンの指定が必要。パッケージが足りなかったら perf
コマンド実行時にエラーが出るので、それを見ると良い。
sudo apt install linux-tools-$(uname -r)
2. カーネルパラメータ設定
root ユーザであれば不要。一般ユーザで実行する際は、以下のような設定が必要になる。
sudo sysctl -w kernel.perf_event_paranoid=1
OS 再起動時にリセットされることに注意。
3. CPUクロック固定
現代のCPUは処理負荷によって CPU クロックが動的に変わる。すると、「プログラムの処理負荷を減らしたのに遅くなった」という不思議現象が稀に良く発生する。これを防ぐためにCPUの動作周波数を固定した方が良い。cpupower
コマンドが使えるだろう(たぶん)。
$ cpupower frequency-info | grep "frequency steps"
available frequency steps: 3.60 GHz, 2.80 GHz, 2.20 GHz
$ sudo cpupower frequency-set -f 3.6GHz
DUBプロジェクトプロファイリング
D言語ユーザであればDUBでプロジェクトを作ることが多いだろう。ということで、DUBプロジェクトでの運用を前提に話を進める。
1. dub.json の設定
プロファイリングを行うためには以下の要素が必要。
- Release ビルドであること
- Debug ビルドのプロファイリングやってもあまり意味がない
- シンボルが含まれていること
- ないと人間が理解できない
- スタックフレームが読めること
というわけで、以下のような設定を dub.json
の "buildTypes"
セクションに追加する。
"buildTypes": {
"perf": {
"buildOptions": ["releaseMode", "debugInfo","optimize", "inline", "alwaysStackFrame"]
}
}
2. ビルド
↑の設定を使ってビルドする。
dub build --build=perf
生成物は dapp
とする。
3. プロファイリング実行
以下のようなコマンドで perf によるプロファイリングを実行する。
perf record -g ./dapp
生成物は perf.data
である。
4. 解析
perf report
コマンドで CUI による解析を行うことができる。慣れればこれでも良いが、ぱっと見で理解するには flamegraph を使うのが良いだろう。使うのは以下の2つ。
それぞれダウンロードして実行権限を与え、以下のようなコマンドを実行すると flamegraph を SVG ファイルとして取得できる。ただし、それだけでは D関数がマングリングされた状態で読みにくいので、ddemangle
によるデマングリングを挟むと良いだろう。
perf script | ./stackcollapse-perf.pl | ddemangle | ./flamegraph.pl > output.svg
ddemangle
はD言語をインストールするとついてくる。
できた output.svg
をブラウザで開くと、良い感じで可視化されている。
実際にやってみた
私が作っている ros2_d というプロジェクトの msg_gen
サブプロジェクトに対してプロファイリングを行ってみた。このプロジェクトについてはまた記事を書く予定だが、やっていることとしてはとある定義ファイルからD言語コードを自動生成する、というものである。
# dub.json は修正済み
# プロファイル用のデータは https://gitlab.com/autowarefoundation/autoware.auto/autoware_auto_msgs を使うことにした。
# share に変えているのは諸事情
mkdir test_data
cd test_data
git clone https://gitlab.com/autowarefoundation/autoware.auto/autoware_auto_msgs.git share
cd ..
export AMENT_PREFIX_PATH=test_data
# 以降ビルド & プロファイリング
dub build :msg_gen --build=perf
perf record -g msg_gen/ros2_d_msg_gen test_data/output
perf script | ./stackcollapse-perf.pl | ddemangle | ./flamegraph.pl > msg_gen.svg
プロファイル結果がこれ。(実際はSVGファイルでインタラクティブに操作できる)
定義ファイルをパースするのに pegged を使っているが、これを呼んでいる処理が大きいようである。とは言え、そもそも速度で困っているわけではないので高速化の予定はない。
rdmd にしてみた。
毎回 カーネル設定したり、flamegraph のツールをダウンロードしたり、 flamegraph の長いコマンドを打つのが面倒なので、 2日目 の記事にあやかって profile.d という名前の rdmd スクリプトにしてみた。これを使うと前節の処理は、前節では省略したカーネル設定とflamegraph ダウンロード含めて次の1行で書ける。
rdmd profile -c -p msg_gen/ros2_d_msg_gen -a "test_data/output" -b ":msg_gen --build=perf" -o msg_gen.svg
さいごに
perf
を使ったD言語アプリのプロファイリングについての紹介はありはするが、DUB での利用や flamegraph 連携、デマングリングなど、実際に使う時にはいつくか手間が必要だと思ったのでこの記事を書いた。とは言え、perf
は最初のボトルネック解析に使われることが多いと思う。実際の高速化作業ではボトルネックポイントの前後にタイマーを仕込み、改善と計測を繰り返すことになるだろう。あとはCPUの気持ちを慮るだけである。
参考文献
- https://wiki.dlang.org/Development_tools
- http://dconf.org/2021/online/slides/haughton.pdf
- https://dub.pm/package-format-json.html#build-types
- https://yohei-a.hatenablog.jp/entry/20150706/1436208007
- https://code.dawg.eu/profiling-with-perf-and-friends.html
- https://kubo39.hatenablog.com/entry/2018/10/25/perf%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6D%E8%A8%80%E8%AA%9E%E3%81%AE%E3%83%97%E3%83%AD%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AA%E3%83%B3%E3%82%B0%E7%B5%90%E6%9E%9C%E3%82%92%E3%81%BF%E3%82%8B