Rustが遅すぎる?プロファイリングで性能向上!

FUJITA Tomonori
nttlabs
Published in
Feb 23, 2022

「開発プロセスにプロファイリングを組み込むのはどうだろう?」

ミーティングで、プロファイリングの重要性を発言するだけで、みんながあなたの深い知見、意識の高さに驚くことでしょう。もちろん、あなたは、プロファイリングのやり方を知っている必要はありません。開発の終盤に、性能目標が達成されず、解析が実施される頃には、誰もあなたの発言は覚えていません。しかし、万が一、あなたの意見が採用されても困らないように、この記事を参考にしてください。

Goは、CPU、メモリ、block、mutexなど、使いこなせないほどの種類をサポートするプロファイリングツールpprofを標準機能として提供します。一方、Rustは、そんな機能を提供しません。Rustへの愛が揺らぐかもしれませんが、Rustへの愛は、見返りを求めない純愛です。愛の見返りに何かが与えられると期待してはいけません。

Rustでもpprof

あなたは、すでに検索サイトで、pprof-rsを見つけているかもしれませんね。Goのpprofに相当するツールが、Rustにもあるはずだ、そう考えることは当然です。しかし、pprof-rsは、CPUプロファイリング機能しかサポートしません。定期的に、スタックトレースを収集し、pprofフォーマットで保存することができます(Flame Graphsフォーマットにも対応)。pprofと同様に、pprof-rsも、ユーザモードで動作し、オペレーティングシステムのタイマー機能を使って、定期的にシグナルを発生させる実装となっています。

性能向上のためには、CPUプロファイリングだけでは不十分です。例えば、Rustの非同期タスクのmutex競合を見つける必要があるかもしれません。Goで可能なら、必要かどうかの問題ではないのです、Rustでもやらなければいけません。

ランタイムとプロファイリング

GoroutineもRustの非同期タスクも、ユーザモードで動作するランタイムが管理するグリーンスレッドと考えることができ、ランタイムが、mutex機能を実装しています。Goでは、処理系にランタイムが含まれていますが、Rustは、複数の非同期ランタイムが開発されており、必要に応じて選択することができます。本記事では、最も人気のあるTokioを使います。

Goで、mutexプロファイリングが有効になっていると、Goroutineがmutexを取得するためにスリープした時間の長さを記録します。つまり、Goroutineがmutexの取得に失敗してから、解放されるまでの時間です。Tokioのmutexコードには、このような機能はありません。しかし、昨年末に、プロファイリングに使えそうな機能が追加されました。

Tracingライブラリ

RustのTracingライブラリは、トレースイベントを取得するための静的なinstrumentationをコードに埋め込み、イベントが発生した際の処理を定義するための仕組みです。単純なロギング、メモリへの保存、ネットワークへの送信、ディスクへの書き込みなど、様々な処理を実現することができます。Tokio以外のプロジェクトでも採用が進んでいます。

Tokioのmutexのコードの様々な箇所に、instrumentationが埋め込まれています。下記は、mutexを開放する擬似コードです。

Tokio's mutex release pseudocode

mutexの取得・解放イベント時に情報を表示する単純なコードを実装してみました。似たようなやり方で、mutex競合を見つけることができそうです。

% cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/profiling-mutex`
03:34:30.070078: Mutex (file="src/main.rs" line=113) Released
03:34:30.070463: Mutex (file="src/main.rs" line=113) Locked
03:34:33.072371: Mutex (file="src/main.rs" line=113) Released
03:34:33.073031: Mutex (file="src/main.rs" line=113) Locked
03:34:34.076974: Mutex (file="src/main.rs" line=113) Released
03:34:34.077524: Mutex (file="src/main.rs" line=113) Locked

表示されているファイル名と行数は、mutexが定義されている場所です。最初の行は、mutexが解放状態で定義されたことを示しています。

Tokioは非同期タスク間の通信など、様々な箇所にinstrumentationを埋め込んでおり、トレース情報を得ることができます。

すでに、トレース情報を使ってみたくなっているでしょうか?アプリケーションへの変更は些細で、取得したいトレースイベントの種類を登録し、イベント発生時の動作を実装するだけです。ただし、以下の注意点があります。

  • Tokioで、Tracingライブラリへの対応はunstable features(いつでもAPIが変わる可能性がある、デフォルトでは有効にならないなどの意味)となっており、特別なオプションをつけて再コンパイルが必要です。
  • Tracingライブラリ自体が、活発に開発されている状況のため、ドキュメントだけでなく、コードも読むことが必要となる可能性が高いです。

一般的には、性能のオーバヘッドを避けるため、トレースイベントをサンプリングし、プロファイリングのために使います。しかし、Tracingライブラリは、様々な機能に対応できる柔軟性のため、その実装がかなり複雑で、そのオーバーヘッドが懸念されます。性能に加え、機能でも、プロファイリングのためには、今後、Tracingライブラリの拡張が必要だと思われます。

まとめ

Rustは、Goのように標準機能としてプロファイリングツールは提供されていませんが、様々なプロファイリングは可能です。また、perfuprobesのようにカーネルモードで動作するプロファイリングツールも利用可能です。

Rustでパフォーマンスを追求したいというエンジニアのみなさま、NTTでは仲間を募集中です。

--

--

FUJITA Tomonori
nttlabs

Janitor at the 34th floor of NTT Tamachi office, had worked on Linux kernel, founded GoBGP, TGT, Ryu, RustyBGP, etc. https://twitter.com/brewaddict