perfでCRubyのプロファイリングができる環境を作る

みなさん、こんにちは。ima1zumi です。

これは Ruby Advent Calendar 2022 6日目の記事です。

本日は macOS で perf が動く仮想環境を作って CRuby のプロファイリングできる環境構築をしていきます。

目次

macOS で perf は使えない

私は普段 macOS で開発しているのですが、perf は Linux 用の計測ツールなので macOS では利用できません。

macOS には Instrument という計測ツールがありますが、情報が少なく調査に難儀するためここでは利用者の多い perf を使います。

というわけで Ubuntu の環境を作っていきますが、 Docker for mac は perf が使えません。そのため、 Virtualbox & Vagrant仮想マシンが動く環境を作ります。Linux 環境の方は「perf をインストールする」まで飛ばしてください。

github.com

Virtualbox & VagrantUbuntu が動く環境を作る

Virtualbox をインストール

brew install --cask vitrualbox または Downloads – Oracle VM VirtualBoxVirtualbox をインストールし、セットアップウィザードを実行します。

Vagrant をインストール

brew install vagrant または Install | Vagrant | HashiCorp DeveloperVagrant をインストールし、セットアップウィザードを実行します。

Box ファイルを入手

次に仮想マシンのベースとなる Box ファイルを入手します。任意のディレクトリで、

vagrant box add ubuntu/jammy64

を実行し、Box ファイルをインストールします。

以下のサイトで様々な Box ファイルが配布されています。

入手した Box ファイルは vagrant box list で確認できます。

❯ vagrant box list
ubuntu/bionic64 (virtualbox, 20220530.0.0)
ubuntu/bionic64 (virtualbox, 20220810.0.0)
ubuntu/jammy64  (virtualbox, 20220810.0.0)
ubuntu/jammy64  (virtualbox, 20221201.0.0)

Vagrantfile を作成

Vagrantfile は仮想マシンの設定ファイルで、メモリやOSなどをどう使うか指定することができます。

Vagrant を起動したいディレクトリで vagrant init ubuntu/jammy64 を実行します。実行後に Vagrantfile ができていればOKです。今回はデフォルトのまま起動します。

Vagrant を起動

vagrant up仮想マシンを起動します。起動後、vagrant status で状態を確認できます。

❯ vagrant status
Current machine states:

default                   running (virtualbox)

The VM is running. To stop this VM, you can run `vagrant halt` to
shut it down forcefully, or you can run `vagrant suspend` to simply
suspend the virtual machine. In either case, to restart it again,
simply run `vagrant up`.

Vagrant に接続

vagrant sshVagrant に接続します。以下のように、Ubuntu にログインできていれば OK です。

❯ vagrant ssh
Welcome to Ubuntu 22.04.1 LTS (GNU/Linux 5.15.0-56-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Mon Dec  5 13:18:02 UTC 2022

  System load:  0.16455078125     Processes:               108
  Usage of /:   3.6% of 38.70GB   Users logged in:         0
  Memory usage: 20%               IPv4 address for enp0s3: 10.0.2.15
  Swap usage:   0%


0 updates can be applied immediately.


vagrant@ubuntu-jammy:~$

perf をインストール

ここでは apt でインストールします。 perf が含まれる linux-tools は Kernel のバージョンに依存するため、以下のコマンドでインストールします。

sudo apt install linux-tools-`uname -r`

Ruby をビルドする

プロファイリングをする際にビルドオプションを変えたりコードに変更を加えたくなるので、手でビルドできるようにします。

依存ライブラリのインストール

ビルドのために

git ruby autoconf bison gcc(or clang, etc) make

が必須です。gcc は clang など他のコンパイラでもOKです。

この環境には Ruby がインストールされていなかったため、Ruby をインストールします。

sudo apt install ruby

次に、拡張ライブラリのためのライブラリをインストールします。必要なライブラリはビルドする Ruby のバージョンによって変わります。rbenv の Wiki に詳しくまとまっています。

Home · rbenv/ruby-build Wiki

ここでは以下のライブラリをインストールします。

sudo apt install autoconf bison patch build-essential rustc libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libgmp-dev libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev uuid-dev

Ruby をビルド

作業用ディレクトリを作ってビルドしていきます。まず RubyソースコードGitHub からクローンします。

mkdir workdir
cd workdir
git clone https://github.com/ruby/ruby.git

github.com

Ruby がクローンできたら ruby ディレクトリに移動して autogen.sh を実行して build ディレクトリを作成します。

cd ruby
./autogen.sh
cd ..
mkdir build
cd build

configure を実行します。configure には色々なコンパイルオプションを渡すことができますが、一旦これでビルドします。

../ruby/configure --prefix=$PWD/../install --enable-shared

make./ruby を生成します。 -j オプションは仮想環境のメモリに余裕がないとコンパイルに失敗しがちだったため、メモリをあまり割り当てていない場合はつけないほうが良さそうです。

make

make install でインストールディレクトリを作成します。

make install

./ruby -v が実行できれば OK です。

./ruby -v
ruby 3.2.0dev (2022-12-06T11:24:02Z master 14074567ea) [x86_64-linux]

perf を実行する

さて、 String#split のプロファイリングをしてみます。性能データを採取するには perf report コマンドを使います。バックトレース情報も含めて採取したいため --cal-graph dwarf をつけます。

String#split の実行時間が短すぎるとうまく採取できず、長すぎると実行結果のファイルが大きくなりすぎるため適当に String#split を実行してその結果を採取します。

sudo perf record --call-graph dwarf -g ~/workdir/install/bin/ruby -e '("あ,い,う,え,お"*100000).split(",")'

実行結果はデフォルトで perf.data ファイルに出力され、 perf report で確認できます。CPU使用率が表示されています。

sudo perf report

Ruby のメソッドに相当する CRuby の関数は rb_ で始まることが多いです。String#split に対応するCRubyの関数は rb_str_split で、その中で rb_str_split_m を実行しています。

rb_str_split_m の中の関数にどれくらいCPUを使用しているか見てみます。見たい行にカーソルをあわせて + キーを押します。

このように詳細が見られます。

また、採取した perf.data を使って flamegraph を出力することもできます。計測において可視化はとても強力なので、ぜひ使ってみてください。

github.com

参考