みなさん、こんにちは。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 をインストールする」まで飛ばしてください。
Virtualbox & Vagrant で Ubuntu が動く環境を作る
Virtualbox をインストール
brew install --cask vitrualbox
または Downloads – Oracle VM VirtualBox で Virtualbox をインストールし、セットアップウィザードを実行します。
Vagrant をインストール
brew install vagrant
または Install | Vagrant | HashiCorp Developer で Vagrant をインストールし、セットアップウィザードを実行します。
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 ssh
で Vagrant に接続します。以下のように、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 に詳しくまとまっています。
ここでは以下のライブラリをインストールします。
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
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 を出力することもできます。計測において可視化はとても強力なので、ぜひ使ってみてください。