コーディングエージェントとプログラミングしていると、パフォーマンスを改善したくなることがある。遅そうな処理を調べるとき、入力が増えたら何回処理が実行されるのか、ループが何重になっているのかを追うことになる。
LLMにコードを読ませても、こういう箇所は見つけられる。ただ、ファイルを広く見て同じような構造を拾う作業は、そこまでLLMに向いた調査ではないのではないかと思った。まず機械的に候補を挙げて、その周辺の意味や改善方法をエージェントと調べられればよさそうだ。
そこで、Rustコードを対象にした軽量な静的解析CLI、nayutaを作った。
ループを手がかりにする
現在のnayutaはRustの構文木を見て、単一のループを O(N)、入れ子のループを O(N^m) の候補として報告する。ファイルと行番号が出るので、そこからコードを読みに行ける。
cargo run -- path/to/project
引数を省略するとカレントディレクトリを再帰的に調べ、target と .git は除外する。cargo run -- --fail src のように --fail を指定すると、検出時に終了コード1を返す。使い方と出力例はREADMEにまとめている。
これは計算量の証明ではない。今はループが入力サイズに依存すると仮定しているので、固定回数のループも候補になる。逆に、イテレーターチェーン、再帰、呼び出し先をまたぐ計算量はまだ扱わない。例えばループの中で呼ぶ関数が入力全体を走査していても、その組み合わせの計算量は分からない。検出されなかったから問題がないとも、検出されたから遅いとも言えない。
候補を見つけた後は、その処理が実際にどれくらい呼ばれるか、入力の大きさはどの程度か、変更して結果が変わらないかを調べる必要がある。このあたりはコードの文脈を読んで、計測やテストもできるコーディングエージェントに任せたい。
メモリアクセスのサンプリング
nayutaには、Linuxの perf mem を使うメモリアクセスの計測機能もある。対応するCPUとカーネルが必要で、ソース行を表示するには対象をデバッグ情報付きでビルドする。
nayuta memory --output run.data -- ./target/release/example argument
nayuta memory --input run.data
結果は正確な読み書き回数ではなくサンプルで、割合もキャッシュミス率ではない。CPUによってサンプリングに偏りがあるため、数字の意味を踏まえて見る必要がある。静的解析の候補と実行時の観測を見ながら、どこを詳しく調べるか決めるための手がかりとして使いたい。