D言語でプロファイリング(perf)

作ったもの : [profile.d](https://gist.github.com/nonanonno/25d556193d59a82218e382e0b038bac3)

# はじめに

D言語には[標準でプロファイリング機能が含まれている](https://wiki.dlang.org/Development_tools#CPU_profiling)。DUBプロジェクトであれば、以下のように実行することで `trace.log` というプロファイル結果を得ることができる。

```shell
dub run --build=profile
```

各関数の最初と最後にタイマーを仕込む形でプロファイリングが行われており、各関数がいつ呼ばれいつ終了したか、何回呼ばれたかなどのメトリクスを取ることができる。 [d-prifile-viewer](https://bitbucket.org/andrewtrotman/d-profile-viewer/src/master/source/app.d) を使うことでこれを可視化できる。

コンパイラに含まれている機能であり気軽に試せるというメリットはありつつも、以下の問題があるため、より良いプロファイリング方法が必要だろう。

- オーバーヘッドが大きい
    - タイマーを仕込む影響
- コールスタックを得ることができない
    - 関数がどこから呼ばれているかがわからない
- Linux では d-profile-viewer を使えない
    - 私は Linux で生きている

そこで本稿では Linux での性能解析でよく使われる `perf` によるプロファイリング方法を解説する。

# perf
## 前提

以下の環境で動作確認を行っている。

- Linux Mint 20 ulyana
- AMD Ryzen 5 3600

Ubuntu でも Intel でも変わらないはず。

## perf とは

ググってくれ。高速化やるか、となった時はまずはだいたいこれを使う。

## 準備

このあたりはD言語に限らない、一般的な話。

### 1. perf インストール

Linux のツールとして提供されている。apt からインストールする。

```shell
sudo apt install linux-tools-generic
```

私の環境ではこれで問題なかった。場合によっては以下のようにカーネルバージョンの指定が必要。パッケージが足りなかったら `perf` コマンド実行時にエラーが出るので、それを見ると良い。

```shell
sudo apt install linux-tools-$(uname -r)
```

### 2. カーネルパラメータ設定

root ユーザであれば不要。一般ユーザで実行する際は、以下のような設定が必要になる。

```shell
sudo sysctl -w kernel.perf_event_paranoid=1
```

OS 再起動時にリセットされることに注意。

### 3. CPUクロック固定

現代のCPUは処理負荷によって CPU クロックが動的に変わる。すると、「プログラムの処理負荷を減らしたのに遅くなった」という不思議現象が稀に良く発生する。これを防ぐためにCPUの動作周波数を固定した方が良い。`cpupower` コマンドが使えるだろう(たぶん)。

```shell
$ 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"` セクションに追加する。

```json
    "buildTypes": {
        "perf": {
            "buildOptions": ["releaseMode", "debugInfo","optimize", "inline", "alwaysStackFrame"]
        }
    }
```

### 2. ビルド

↑の設定を使ってビルドする。

```shell
dub build --build=perf
```

生成物は `dapp` とする。

### 3. プロファイリング実行

以下のようなコマンドで perf によるプロファイリングを実行する。

```shell
perf record -g ./dapp
```

生成物は `perf.data` である。

### 4. 解析

`perf report` コマンドで CUI による解析を行うことができる。慣れればこれでも良いが、ぱっと見で理解するには [flamegraph](https://www.brendangregg.com/flamegraphs.html) を使うのが良いだろう。使うのは以下の2つ。

- [flamegraph.pl](https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl)
- [stackcollapse-perf.pl](https://github.com/brendangregg/FlameGraph/blob/master/stackcollapse-perf.pl)

それぞれダウンロードして実行権限を与え、以下のようなコマンドを実行すると flamegraph を SVG ファイルとして取得できる。ただし、それだけでは D関数がマングリングされた状態で読みにくいので、`ddemangle` によるデマングリングを挟むと良いだろう。

```shell
perf script | ./stackcollapse-perf.pl | ddemangle  | ./flamegraph.pl > output.svg
```

`ddemangle` はD言語をインストールするとついてくる。

できた `output.svg` をブラウザで開くと、良い感じで可視化されている。

## 実際にやってみた

私が作っている [ros2_d](https://github.com/nonanonno/ros2_d) というプロジェクトの `msg_gen` サブプロジェクトに対してプロファイリングを行ってみた。このプロジェクトについてはまた記事を書く予定だが、やっていることとしてはとある定義ファイルからD言語コードを自動生成する、というものである。

```shell
# 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ファイルでインタラクティブに操作できる)

![msg_gen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73600/b08cd3bb-a66f-099e-3d9d-a9a3bd1f54d9.png)



定義ファイルをパースするのに [pegged](https://code.dlang.org/packages/pegged) を使っているが、これを呼んでいる処理が大きいようである。とは言え、そもそも速度で困っているわけではないので高速化の予定はない。

## rdmd にしてみた。

毎回 カーネル設定したり、flamegraph のツールをダウンロードしたり、 flamegraph の長いコマンドを打つのが面倒なので、 [2日目](https://qiita.com/lempiji/items/0730a6441f4f23a57119) の記事にあやかって [profile.d](https://gist.github.com/nonanonno/25d556193d59a82218e382e0b038bac3) という名前の rdmd スクリプトにしてみた。これを使うと前節の処理は、前節では省略したカーネル設定とflamegraph ダウンロード含めて次の1行で書ける。

```shell
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