PerfViewとdotnet-traceで「遅い」を特定する ── .NETパフォーマンス調査の実務入門
· 小村 豪 · PerfView, dotnet-trace, ETW, パフォーマンス調査, .NET, CSharp, 不具合調査, Windows開発, 技術相談
前回の「Windowsイベントログ・ETW入門」では、ETWが何であるかと、EventSource で独自イベントを定義するところまでを扱い、PerfViewでの解析は「範囲外」としました。この記事はその続きです。業務アプリが「遅い」「CPUが張り付く」「たまに固まる」ときに、PerfViewとdotnet-traceで原因の関数まで特定する手順を扱います。
なお、この記事はCPU・応答時間の調査が主題です。メモリが増え続ける問題の切り分けは「.NETでGC待ちとメモリリークを見分ける」、クラッシュしてしまう問題は「WinDbg + SOSでクラッシュダンプを読む」で扱っています。
1. まず結論
- 「遅い」には2種類あります。CPUを使い切って遅い(CPU bound)か、CPUは暇なのに待たされて遅い(ブロック)かです。 前者はCPUサンプリング、後者はThreadTime(コンテキストスイッチ)の収集が必要で、既定のCPU収集だけ見ても後者の原因は写りません。1
- 迷ったら dotnet-trace から始めます。 EventPipeベースのクロスプラットフォームツールで、対象プロセスを起動したのと同じユーザーであれば管理者権限なしで収集できます。2 収集した
.nettraceはPerfViewやVisual Studioで開けます。3 - PerfViewが必要になるのは、マシン全体・ネイティブ・ブロック時間まで見たいときです。 ETWベースなので、カーネルイベントやネイティブコードのスタックも扱えます。その代わり、ETWセッションの開始には管理者権限が必要です。3
- CPUサンプリングは既定で1ミリ秒ごと(プロセッサごと)です。 1サンプル=約1msのCPU時間として読み、目安として1,000サンプル以上(できれば5,000程度)集まってから判断します。サンプル数が少ないうちの上位関数は偶然の可能性があります。1
- 数字の解釈は inclusive(自分+呼び出し先) と exclusive(自分自身) の区別がすべての起点です。 exclusiveが大きい関数が「実際にCPUを使っている場所」、inclusiveが大きい関数は「その配下のどこかが重い場所」です。
- 測る前に、何が「遅い」のかを1つの操作に固定します。 「全体的に重い」のまま収集しても読めません。「この帳票の出力に40秒かかる」のように操作と時間を固定してから収集します。改善前後の比較計測の考え方は「Windowsでプログラムのバージョン別速度を正しく比較する方法」も参考になります。
症状からの入り口を判断表にまとめます。
| 症状 | 最初に使う道具 | 見るもの |
|---|---|---|
| CPUが高いまま張り付く | dotnet-trace collect / PerfViewのCPU Stacks | exclusiveの大きい関数 |
| CPUは低いのに処理が遅い・固まる | PerfView(ThreadTime収集) | どのスレッドが何を待ってブロックしたか |
| 遅いが、まずは傾向だけ知りたい | dotnet-counters | CPU使用率、GC頻度、ThreadPoolキューの詰まり |
| GCが多すぎる疑い | dotnet-counters → dotnet-trace(GCイベント) | GC回数・停止時間、割り当ての多い場所 |
| 特定の業務処理のどこが遅いか知りたい | EventSourceの独自イベント+上記 | 独自イベント間の経過時間とその区間のスタック |
2. 道具の関係を整理する ── ETWとEventPipe
登場するツールは多く見えますが、土台は2つしかありません。
- ETW(Event Tracing for Windows): OS全体を貫くトレース基盤。カーネルからアプリまで同じ時間軸で記録できる反面、セッションの開始・停止に管理者権限が必要で、Windows専用です。3
- EventPipe: .NETランタイムに組み込まれたトレース機構。管理者権限が不要で全OSで同じように動く代わりに、見えるのはマネージドコードとランタイムの範囲に限られ、カーネルイベントやネイティブスタックは取れません。2
| dotnet-trace(EventPipe) | PerfView(ETW) | |
|---|---|---|
| 管理者権限 | 不要(同一ユーザーのプロセスに対して) | 必要 |
| 対象 | 指定した.NETプロセス1つ | マシン全体(全プロセス+カーネル) |
| ネイティブスタック | 取れない | 取れる |
| ブロック時間(コンテキストスイッチ) | 取れない | ThreadTime収集で取れる |
| 対応OS | Windows / Linux / macOS | Windowsのみ |
この対応関係が頭に入っていると、「まずdotnet-traceで軽く取る。足りなければPerfViewでETWごと取る」という使い分けが自然に決まります。
3. 収集の前に ── dotnet-countersで傾向を10分見る
いきなりトレースを取るより、まず dotnet-counters でランタイムの主要メトリクスを眺めるほうが早いことが多いです。4
dotnet tool install --global dotnet-counters
dotnet-counters ps
dotnet-counters monitor --process-id <PID> --counters System.Runtime
ここで見るのは、CPU使用率、GCヒープサイズとGC回数、ThreadPoolのキュー長・スレッド数、例外数です。この時点で「GCが毎秒何度も走っている」「ThreadPoolのキューが伸び続けている」といった傾向が見えれば、次に取るべきトレースの種類(割り当てなのか、ブロックなのか)を絞れます。メモリ系の傾向の読み方は GC待ちとメモリリークの見分け方の記事 で詳しく書いています。
4. dotnet-traceでトレースを取る
dotnet-trace はEventPipeベースの収集ツールです。5 インストールと基本の収集は次のとおりです。
dotnet tool install --global dotnet-trace
dotnet-trace ps
dotnet-trace collect --process-id <PID> --duration 00:00:00:30
オプションを何も指定しない場合、ランタイムの主要イベントとスレッドのサンプリングを含む既定のプロファイル構成で収集されます。かつて存在した cpu-sampling という名前のプロファイルは廃止され、現在は dotnet-common と dotnet-sampled-thread-time の組み合わせが既定です。5 用途に応じて --profile gc-verbose(GCとオブジェクト割り当てのサンプリング)や --profile gc-collect(GCの発生のみを低負荷で記録)も選べます。
前回の記事で作った EventSource の独自プロバイダーを一緒に収集するには、--providers を追加します。
dotnet-trace collect --process-id <PID> --providers KomuraSoft-OrderService
収集結果は .nettrace ファイルになります。開き方は3通りあり、Visual StudioかPerfViewで開くか、speedscope形式に変換してブラウザーで見ます。5
dotnet-trace convert trace.nettrace --format Speedscope
speedscope形式は speedscope.app で開ける軽量なフレームグラフ表示で、「どの関数の配下で時間を使ったか」を直感的に眺めるのに向いています。関数名まで特定した精査はPerfViewのスタックビューアーのほうが強力なので、次章に進みます。
5. PerfViewでCPUを調べる ── スタックビューアーの読み方
PerfViewはMicrosoftが無償公開している性能調査ツールで、GitHubのリリースページから PerfView.exe 1つをダウンロードするだけで動きます(インストール不要)。6
5.1 収集する
GUIの「Collect > Collect」でダイアログを開き、Start Collection → 対象の操作を再現 → Stop Collection が基本の流れです。コマンドラインなら次の形になります(ETWセッションを開始するため、管理者として実行します3)。
PerfView collect /nogui /acceptEULA /maxCollectSec:30
既定の収集は、全プロセッサで1ミリ秒ごとのCPUサンプル(コールスタック付き)と、CLRの主要イベントを含みます。1 収集中のオーバーヘッドは既定構成でおおむね数%程度とされています。7
5.2 CPU Stacksを読む
収集結果(.etl.zip)を開き、「CPU Stacks」で対象プロセスを選ぶと、スタックビューアーが開きます。最初に見るのは By Name タブの2つの列です。
- Exc(exclusive): その関数自身が実行中だったサンプル数。実際にCPUを消費している場所です。
- Inc(inclusive): その関数と、そこから呼ばれた先すべてのサンプル数の合計。その配下のどこかが重いことを示します。
読み方の型は、Excの上位から見て「自分のコードか、ランタイム・ライブラリか」を仕分けることです。自分のコードがExc上位に出ていれば、その関数のアルゴリズムやループが直接の対象です。System.String 系や JSON シリアライザーがExc上位なら、CallersタブでそれをIncで辿って、自分のコードのどこから大量に呼んでいるかを突き止めます。
もう1つ重要なのがサンプル数そのものの確認です。CPUサンプルは1ms間隔の統計なので、サンプル数が少ないと偶然に左右されます。目安として合計1,000サンプル以上、できれば5,000程度を集めてから判断し、足りなければ対象の操作を繰り返し実行して収集時間を延ばします。1
なお、自社モジュールの関数名がアドレス表示になってしまう場合は、シンボル(PDB)が解決できていません。ビルド成果物のPDBを手元に揃えておくことが、そのまま調査可能性になります。この話は「PDB(プログラムデータベース)とは何か」で詳しく書いています。
6. 「CPUは暇なのに遅い」 ── ThreadTimeでブロック時間を見る
実務の「遅い」の半分以上は、CPUではなく待ちです。ロック待ち、DB・HTTP応答待ち、ファイルI/O、Task.Result によるデッドロック気味の待ち合わせ。この種の問題は、CPUサンプルには「そもそもCPUを使っていない時間」として写らないため、既定収集では原因が見えません。
PerfViewでは、収集時に Thread Time オプションを有効にすると、コンテキストスイッチのイベントが追加で記録され、各スレッドが「CPUを使っていた時間」と「ブロックしていた時間」を合わせて追えるようになります。1
PerfView collect /nogui /acceptEULA /threadTime /maxCollectSec:30
解析では「Thread Time Stacks」ビューを開きます。CPU Stacksと同じスタックビューアーですが、サンプルがCPU時間だけでなくBLOCKED_TIME(ブロックしていた時間)も含む点が違います。遅い操作の時間範囲に絞り込んだうえで、処理を担っていたスレッドのBLOCKED_TIMEがどのスタック(どの待ち)に積まれているかを見ると、「合計40秒のうち35秒はこのHTTP呼び出し待ちだった」という形で内訳が出ます。
UIが固まる系の症状(操作すると数秒応答なしになる)も、正体はUIスレッドのブロックであることがほとんどです。ThreadTimeでUIスレッドのブロック先を特定したうえで、恒久対策としては同期待ちを非同期化する設計に持ち込みます。この設計側の整理は「WPF/WinFormsのasyncとUIスレッドを一枚で整理」を参照してください。
注意点として、ThreadTime収集はコンテキストスイッチごとにイベントを記録するため、既定収集よりデータ量が明確に増えます。収集時間は短く区切り、本番での長時間収集は避けてください。
7. EventSourceと組み合わせる ── 「どの区間が遅いか」を業務の言葉で切る
CPU StacksもThread Timeも、プロセス全体の集計です。「注文処理1件の中のどこが遅いか」のように業務処理の単位で切りたい場合は、前回の記事で扱った EventSource のStart/Stopイベントが効いてきます。
- アプリ側で
OrderProcessingStart/OrderProcessingStopのようなイベントを計装しておく(実装は 前回の記事 を参照) - PerfViewの「Events」ビューでそのイベントの時刻を確認し、スタックビューアーの時間範囲(Start/Endフィルター)をその区間に絞る
- 絞った範囲でCPU/ブロック時間の内訳を読む
こうすると「遅かったその1回」だけを対象に分析でき、平常時のノイズに埋もれません。独自イベントはdotnet-traceでも同じように収集できるため、計装を一度入れておけば、開発機でも本番でも同じ調べ方が通用します。パフォーマンス調査は、ツールの腕前より「アプリ側に観測点があるか」で難易度が決まる、というのが実務での実感です。
まとめ
- 「遅い」をCPU boundとブロックに切り分けるのが出発点です。前者はCPUサンプリング(1サンプル≒1ms)、後者はThreadTime収集でなければ写りません。
- まずは管理者権限なしで使えるdotnet-trace、マシン全体・ネイティブ・ブロック時間が必要ならPerfView、という使い分けが基本です。収集した
.nettraceをPerfViewで読む併用も定番です。 - スタックビューアーはexclusiveとinclusiveの区別が読み方の核で、サンプル数が十分あるかを常に確認します。
- EventSourceの独自イベントで業務処理の区間を切れるようにしておくと、調査の精度が一段上がります。
合同会社小村ソフトでは、「遅い」「固まる」「CPUが張り付く」といったWindows業務アプリの性能問題の調査と、計測しやすくするための計装・設計の相談を扱っています。再現手順とトレースファイルがあれば、そこからの解析のみのご依頼も可能です。
関連記事
- Windowsイベントログ・ETW入門 ── 業務アプリのログをOS標準の仕組みに乗せる
- .NETでGC待ちとメモリリークを見分ける ── 増えるメモリを観測・比較・証明する実務手順
- WinDbg + SOSでクラッシュダンプを読む ── 収集した後の実務解析入門
- Windowsクラッシュダンプ収集入門 - WER/ProcDump/WinDbg
- PDB(プログラムデータベース)とは何か ── デバッグ情報・シンボル・Source Linkを理解する
- Windowsでプログラムのバージョン別速度を正しく比較する方法
- WPF/WinFormsのasyncとUIスレッドを一枚で整理
関連する相談領域
参考リンク
-
GitHub, PerfView User’s Guide. 既定収集がプロセッサごとに1ミリ秒間隔のCPUサンプル(コールスタック付き)であること、判断には1,000〜5,000サンプル程度が望ましいこと、ThreadTimeオプションでコンテキストスイッチを収集しブロック時間まで分析できることについて(PerfView本体のHelpからも参照可能)。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, EventPipe Overview. EventPipeが管理者権限のような高権限コンポーネントに依存せずに.NETアプリをトレースするための仕組みであること、対象がマネージドコードとランタイムに限られ、カーネルイベントやネイティブスタックは取得できないことについて。 ↩ ↩2
-
Microsoft Learn, Collect and View EventSource Traces. ETWのトレース収集には常に管理者権限が必要であること、PerfViewやVisual Studioが.nettraceファイルを開けることについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, dotnet-counters diagnostic tool. System.Runtimeカウンターによる実行中プロセスのCPU・GC・ThreadPool等のメトリクス監視について。 ↩
-
Microsoft Learn, dotnet-trace diagnostic tool. collectコマンドの使い方、既定で有効になるプロファイル(dotnet-common / dotnet-sampled-thread-time)とcpu-samplingプロファイルの廃止、gc-verbose / gc-collectなどのプロファイル、Speedscope / Chromium形式への変換について。 ↩ ↩2 ↩3
-
GitHub, microsoft/perfview. PerfViewがCPU・メモリ関連の性能問題を調査するための無償の性能分析ツールであり、リリースページから実行ファイルを直接入手できることについて。 ↩
-
GitHub, microsoft/perfview Issue #598. PerfViewメンテナーによる、既定収集時のオーバーヘッドがおおむね3%程度であるという説明、および/CpuSampleMSecでサンプリング間隔を調整して負荷を下げられることについて。 ↩
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Windowsイベントログ・ETW入門 ── 業務アプリのログをOS標準の仕組みに乗せる
Windows業務アプリのログをファイルログだけで済ませていないでしょうか。イベントログとETWは、運用担当者やOS標準ツールから見える別レイヤーの記録です。3手段の使い分け、.NETからの書き込み方法、EventSourceによるETW計装、収集・調査の実務、ソース未登録...
appsettings.jsonだけじゃない ── Windows業務アプリの構成管理実務(環境別設定・秘密情報・書き込み先)
Windows業務アプリの構成管理を、appsettings.jsonの階層化、IConfigurationとIOptions/IOptionsSnapshot/IOptionsMonitorの使い分け、環境別設定、書き込み可能な設定の置き場所、秘密情報の扱い、実行中の設定...
Windows業務アプリの印刷とPDF出力 ── System.Drawing.Printing / WPF / 帳票ライブラリの使い分け
PrintDocumentによるWinForms印刷、WPFのFlowDocument/FixedDocument印刷、PDF出力の選択肢を、要件別の判断表として整理します。改ページ制御やDPIのずれといった実装の落とし穴、Windowsサービスからの印刷、既定プリンター依...
WinDbg + SOSでクラッシュダンプを読む ── 収集した後の実務解析入門
収集したWindowsクラッシュダンプをWinDbgとSOS拡張で実際に読む手順を解説します。シンボルパスの設定、!clrstack・!pe・!dumpheap -stat・!gcrootによる例外とメモリリークの追い方、ネイティブクラッシュの!analyze -v、dot...
Windowsアプリ 外注・受託開発を依頼する前に整理したいこと
Windowsアプリの外注・受託開発を依頼する前に、既存ソフト改修、装置連携、COM/ActiveX、配布・更新、保守の整理ポイントを解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
不具合調査 / 長期稼働テーマ
再現しにくい不具合、通信停止、長期稼働障害、失敗パス検証を整理するトピックです。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
不具合調査・原因解析
「遅い」「固まる」の原因特定はパフォーマンス起因の不具合調査そのものだからです。
Windowsアプリ開発
計測しやすいアプリの設計(EventSourceによる計装など)はWindowsアプリ開発の相談範囲だからです。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- PerfViewとdotnet-trace、どちらを使うべきですか?
- まずdotnet-traceから試すのがおすすめです。管理者権限なしで使え、対象プロセスだけを狙って収集でき、操作もコマンド1行で済みます。一方、ネイティブコードのスタックまで見たい場合、マシン全体の状況(他プロセスやカーネルの影響)まで見たい場合、ブロック時間をコンテキストスイッチ単位で追いたい場合は、ETWベースのPerfViewが必要です。収集した.nettraceファイルをPerfViewで開いて分析する、という併用も日常的に行います。
- Visual Studioのプロファイラーとの関係はどうなりますか?
- 開発機で再現できる問題なら、Visual StudioのCPU使用率ツールやメモリツールのほうが手軽で、まずそちらで十分です。PerfViewやdotnet-traceが効くのは、Visual Studioを入れられない検証機・本番機で収集する場面、収集と分析を別マシンで分けたい場面、ETWのイベント(独自EventSourceやカーネルイベント)まで含めて見たい場面です。なお、Visual Studioはdotnet-traceが出力する.nettraceファイルを開くこともできます。
- 本番サーバーで実行しても大丈夫ですか?
- どちらのツールも本番での短時間収集を想定して作られていますが、無条件ではありません。収集時間を数十秒〜数分に区切る、まず検証環境で同じコマンドを試してオーバーヘッドとファイルサイズを確認する、ディスク空き容量を確認する、という手順を踏んでください。特にThreadTime(コンテキストスイッチ)収集や割り当てトレースはイベント量が多く、ファイルが急速に大きくなります。また、トレースにはコマンドライン引数などの情報が含まれるため、取得ファイルの扱いにも注意が必要です。
- WPR/WPAとはどう使い分けますか?
- WPR(Windows Performance Recorder)とWPA(Windows Performance Analyzer)は、同じETWを土台にした、よりOS寄りの調査ツールです。ディスクI/O・電源・起動時間などOS全体の詳細分析ではWPR/WPAが強く、.NETアプリのCPU・GC・ブロック時間の調査ではマネージドコードの表示に最適化されたPerfViewのほうが読みやすいです。業務アプリの調査が目的なら、PerfViewとdotnet-traceで大半は足ります。
著者プロフィール
記事の著者プロフィールページです。
小村 豪
合同会社小村ソフト 代表
Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。
公開リンク