サンプル プロファイルガイド付き最適化 (SPGO) を使用して C++ のパフォーマンスを向上させる

Profile-Guided 最適化 (PGO) はランタイム データを使用して、コンパイラがより適切な最適化の決定を行うのに役立ちます。 PGO は、代表的なワークロードから収集された実行プロファイル データを使用して、インライン化、コード レイアウト、ホット/コールド コードの分離に関するよりスマートな決定を行うことができます。 これらの決定は、静的分析だけでは行えなくなります。

SPGO には別のアプローチがあります。 SPGO では、バイナリをインストルメント化して合成トレーニング シナリオを実行する代わりに、実際のリリース バイナリから収集されたハードウェア パフォーマンス カウンター サンプリングが使用されます。 最新のプロセッサは、ハードウェア サンプリング機能を提供します。 これらのサンプルはごくわずかのランタイム オーバーヘッドで収集できるため、実稼働コードから直接ランタイム プロファイルを収集するのが実用的です。

SPGO プロファイルはインストルメント化されたビルドではなくビットを解放するため、データを収集する場所と方法の柔軟性が大幅に向上します。 運用サーバー、開発者マシン、パフォーマンス ラボ、または任意の組み合わせからランタイム プロファイルを収集できます。 その結果、ホット パスをより効率的に実行するバイナリが生成され、プロファイル データの品質に応じて一般的なパフォーマンスが 5 から 15% 高速化されます。

このチュートリアルでは、サンプル アプリの構築、 xperfを使用したプロファイリング、プロファイル データの準備、プロファイル データを使用したリビルドなど、完全な SPGO ワークフローについて説明します。 完了したら、独自のプロジェクトに同じプロセスを適用できます。

[前提条件]

開始する前に、次のソフトウェアとハードウェアがあることを確認します。

Software

  • x64/x86/ARM64 v14.51 以降用のMSVC ビルド ツール- Visual Studio インストーラーを使用してインストールします。 [ 個々のコンポーネント] で、"MSVC ビルド ツール" を検索します。
  • Windows Performance Toolkit (xperf.exe)xperf プロファイラーは、プログラムの実行中にサンプル データを収集します。 Windows Assessment and Deployment Kit (ADK) を ADK install からダウンロードします。 ADK インストーラーを実行するときに、Windows Performance Toolkit コンポーネントを選択して、xperfを取得します。 完全な ADK をインストールする必要はありません。
  • War および Peace テキスト ファイル - プロファイリング データを生成するためのサンプル ワークロードとして使用されます。 グテンベルクProjectからダウンロードします: https://www.gutenberg.org/ebooks/2600。 作業ディレクトリにプレーン テキスト ファイルとして保存します。

ハードウェア要件

このチュートリアルには、3 つのプロファイリング パスがあります。 どのパスを使用するかは、ハードウェアによって異なります。 検出コマンドは、 プロファイル方法の選択 で実行し、マシンがサポートするパスを確認します。 ここでは、次の表を使用して、要件の少なくとも 1 つを満たしていることを確認します。

Path CPU要件 メモ
LBR (最良の結果) Last Branch Records (LBR) は、Intel Haswell CPU (第 4 世代コア、2013 年) 以降で提供されるパフォーマンス カウンターです。AMD Zen 4 (2022) 以降、ARM64 ARMv9.2-A (2020) 以降 最適なブランチ データを提供します。 LBR の詳細については、「最後の分岐レコードの概要」を参照してください。
PMC/IP モード (適切な結果) パフォーマンス監視カウンター (PMC) は、パフォーマンス監視ユニット (PMU) を備えた任意の x64 CPU でサポートされます LBR が使用できない最新の CPU で動作します。 PMC の詳細については、「ハードウェア パフォーマンス (PMU) イベントの記録」および「完全な例を使用したハードウェア パフォーマンス (PMU) イベントの記録」を参照してください。
OS タイマー (どこでも動作) Azure VM と仮想マシンを含む x64 または ARM64 CPU 忠実度の低いサンプルですが、常に使用可能

最新の x64 デスクトップ ハードウェアのほとんどの開発者は、LBR をサポートしています。 VM と一部の古いハードウェアには、PMC または OS タイマーがあります。

SPGO のしくみ

SPGO は、実行中のバイナリからプロファイル データを収集し、次のビルド時にコンパイラにフィードします。 コンパイラはそのデータを使用して、インライン化、コード レイアウト、分岐予測に関するより適切な決定を行います。 便利なのは、インストルメンテーションは必要ないということです。

ワークフローは次のとおりです。

  1. /spgo リンカー フラグを使用してバイナリをビルドします。 この手順では、空の サンプル プロファイル データベース (.spd ファイル) を作成します。
  2. xperfを使用してバイナリをプロファイリングし、ETL トレース ファイルを生成します。
  3. を使用して ETL を SPTAggregate.exe ファイルに変換し、を使用して SPT を SPDConvert.exe ファイルに変換します。
  4. 設定されたサンプル プロファイル データベース (SPD) を指す /spdin リンカー フラグを使用して再構築します。 リンカーは SPGO 最適化を適用します。

オプティマイザーは SPD を使用して、次のような質問に答えます。どの分岐が最も頻繁に使用されますか? ホット ループで呼び出される関数はどれですか? このプロセスにより、静的分析だけで行うよりも、コード レイアウトとインライン化の決定が向上します。

SPGO は C と C++ の両方で動作します。 ワークフローとフラグは、両方の言語で同じです。

SPGO の最適な候補: 緊密な内部ループを持つ大規模なブランチで満たされた C/C++ アプリケーション。 コードベースのサイズと分岐の複雑さによってスケーリングを実現します。 このチュートリアルの小さなサンプルでは、約 7% の改善が示されています。 運用コードベースが大きいほど、多くの場合、改善が見られます。

ビルドプロセスの比較

このセクションでは、のしくみを理解する必要がある場合に、SPGO がビルド パイプラインにどのように適合するかを説明します。

通常のビルド プロセス

標準の C/C++ リリース ビルドでは、次の手順を実行します。

  • 入力: ソース コード ファイル (.cpp.h) とリリース モードコンパイラ フラグ (/O2/GLなど)。
  • プロセス: コンパイラは、ヒューリスティックのインライン化、分岐予測の前提条件、静的分析だけに基づくコード レイアウトの決定などの標準的な最適化を適用します。 実行時のプログラムの実際の動作に関するデータはありません。
  • 出力: 実行可能ファイル (.exe)、DLL ファイル (.dll)、デバッグ情報 (.pdb)。

ビルド ステップにフローする入力としてソース コード ファイルとコンパイラ スイッチ /GL の例を示す通常のリリース ビルド プロセスの図。.exe、.dll、.pdb 出力が生成されます。

ランタイム データがないと、ホット パスとコールド パスも同様の処理を受けます。

SPGO 対応のビルド プロセス

SPGO は、プロファイル データを新しい入力としてビルド パイプラインに追加します。

  • 入力: ソース コード、 .spd プロファイル ファイル (プロファイル実行からのサンプル数)、リリース モードコンパイラ フラグ、 /link /spgo、および入力 SPD ファイルを指定するための /spdin:<path> (指定しない場合、既定ではバイナリ名を使用して .spd に設定され、obj フォルダーにあります)。
  • プロセス: リンカーは、中間コードと共に SPD を読み取ります。 分岐頻度データを使用して、インライン化、コード レイアウト、分岐順序の決定を改善します。 ホット関数は、高速アクセスのためにレイアウトされます。コールド コードはクリティカル パスから移動します。
  • 出力: 最適化された実行可能ファイル (.exe)、最適化された DLL ファイル (.dll)、デバッグ情報 (.pdb)、および将来のプロファイリングイテレーション用の 新しい .spd ファイル

追加のリンカー スイッチ /spgo を使用して、ソース コードとプロファイル データ ファイル (.spd) をビルド ステップへの入力として示す SPGO 対応のビルド プロセスの図。ビルド プロセスでは、最適化された .exe、.dll、デバッグ情報 (.pdb)、および新しいプロファイル データ ファイル (.spd) が出力されます。

重要な分析情報: SPGO は、最適化の決定をコンパイラとリンカーヒューリスティックから、実際の実行に基づくデータドリブンの選択肢に移動します。

主要フラグ

フラグ タイプ Purpose
/spgo Linker SPGO を有効にします。 SPGO メタデータをバイナリに埋め込み、.spdを指定しない限り、空の/spdin出力ファイルを作成します。この場合、指定した.spd ファイルが入力として使用されます。
/spdin:<path> Linker 入力 SPD - 最適化のためにリンカーにプロファイル データを提供します
/spd:<path> Linker 出力 SPD パス - 新しい SPD を書き込む場所を指定します (省略可能。既定値はバイナリと同じディレクトリです)。 /spdinが指定されていない場合は、入力 SPD パスとして機能します。
/GL コンパイラ SPGO が翻訳単位間で機能するためには、プログラム全体の最適化が必要です
/O1/O2 (サイズの最小化、速度の最大化) コンパイラ 速度に合わせて最適化する。は、SPGO が強化できる積極的な最適化を可能にします。

SPGO と PGO の違い

PGO(プロファイル誘導最適化)では、インストルメンテーションフラグ(/GENPROFILE)を使用してバイナリをコンパイルし、通常より低速なインストルメントされたバイナリを実行して .pgc 実行回数カウントファイルを収集し、その後 /USEPROFILE を使用して再リンクする必要があります。 コンパイラは正確な実行数を取得しますが、最初にコードをインストルメント化する必要があります。 このプロセスの詳細については、「 プロファイルガイド付き最適化」を参照してください。

SPGO は、ハードウェア CPU パフォーマンス カウンターを用いて、インストルメントされていないリリース版バイナリから統計サンプルを収集します。 既存のバイナリを実行し、 xperfを使用してプロファイリングし、トレースを SPD ファイルに変換して再構築します。 インストルメント化されたビルドはなく、プロファイリング中の速度低下もありません。 コンパイラは正確なカウントではなく統計サンプリング データを取得します。これは正確ではありませんが取得が容易で、コードを変更する必要はありません。 また、インストルメント化されたアプローチではデータの収集が困難なシステム コンポーネントまたはリアルタイム コンポーネントのプロファイリングも可能です。 最終的なバイナリまたは出荷バイナリをプロファイリングすることもできます。

このチュートリアルでは、LBR、PMC、OS タイマーの 3 つのプロファイリング方法について説明します。 [プロファイル方法の 選択] で方法を選択します。 フラグ参照テーブルを含む、通常のビルド プロセスと SPGO ビルド プロセスの詳細な比較については、「 ビルド プロセスの比較」を参照してください。

perfcore.ini のコンフィギュレーション

⚠️ 必須: この手順がないと、 xperf は必要なプロファイリング データを提供しません。 xperfを実行する前に、この手順を完了してください。

Windows Performance Toolkit (WPT) は、perfcore.ini を使用して、SPGO に必要な DLL プロバイダーを登録するために、C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\perfcore.ini の既定の場所に WPT をインストールした場合に配置されます。

管理者として Windows のメモ帳を開きます。 次に、 perfcore.iniを開きます。 DLL リスト セクションを見つけて、次のエントリを 1 行に 1 つずつ追加します。

perf_spt.dll
perf_lbr.dll

xperf.exeがインストールされていない場合は、「一般的な問題」を参照してください。

perfcore.iniを保存して閉じます。 DLL ファイルは既に xperf.exe と同じディレクトリに格納されているため、どこにもコピーする必要はありません。 perfcore.iniにのみ登録しているだけです。 xperfがパスにあることを確認します。

サンプル アプリを作成する

このチュートリアルのサンプル アプリは、標準入力からテキストを読み取り、行数、単語数、合計文字数、文字頻度テーブル、およびファイルを処理するための経過時間をミリ秒単位で生成する C++ プログラムです。 これは C++ で記述されていますが、SPGO は C でも動作します。ワークフローは C プロジェクトと同じです。

作業ディレクトリに textCount.cpp という名前のファイルを作成し、次のソース コードを追加します。

// textCount.cpp : Text Statistics Counter
// Counts words, lines, and character frequencies from standard input
// Usage: textCount < file.txt

#include <iostream>
#include <string>
#include <map>
#include <cctype>
#include <chrono>

int main()
{
    auto start = std::chrono::steady_clock::now();

    std::map<unsigned char, int> charFrequency;
    int wordCount = 0;
    int lineCount = 0;
    int totalChars = 0;

    std::string line;
    bool inWord = false;

    while (std::getline(std::cin, line))
    {
        lineCount++;

        for (char c : line)
        {
            totalChars++;
            unsigned char uc = static_cast<unsigned char>(c);
            charFrequency[uc]++;

            if (std::isspace(static_cast<unsigned char>(c)))
            {
                inWord = false;
            }
            else
            {
                if (!inWord)
                {
                    wordCount++;
                    inWord = true;
                }
            }
        }

        inWord = false;
    }

    std::cout << "\n=== TEXT STATISTICS ===" << std::endl;
    std::cout << "Lines: " << lineCount << std::endl;
    std::cout << "Words: " << wordCount << std::endl;
    std::cout << "Total Characters: " << totalChars << std::endl;

    std::cout << "\n=== CHARACTER FREQUENCIES ===" << std::endl;

    std::cout << "\nLetters:" << std::endl;
    for (unsigned char ch = 'a'; ch <= 'z'; ch++)
    {
        unsigned char upperCh = static_cast<unsigned char>(std::toupper(ch));
        int count = charFrequency[ch] + charFrequency[upperCh];
        if (count > 0)
        {
            std::cout << static_cast<char>(ch) << ": " << count << std::endl;
        }
    }

    std::cout << "\nDigits:" << std::endl;
    for (unsigned char ch = '0'; ch <= '9'; ch++)
    {
        if (charFrequency[ch] > 0)
        {
            std::cout << static_cast<char>(ch) << ": " << charFrequency[ch] << std::endl;
        }
    }

    std::cout << "\nSpecial Characters:" << std::endl;
    for (const auto& pair : charFrequency)
    {
        unsigned char ch = pair.first;
        if (!std::isalnum(ch))
        {
            std::string displayChar;
            switch (ch)
            {
                case ' ': displayChar = "[space]"; break;
                case '\t': displayChar = "[tab]"; break;
                case '\n': displayChar = "[newline]"; break;
                case '\r': displayChar = "[return]"; break;
                default:
                    if (ch >= 32 && ch < 127)
                    {
                        displayChar = std::string(1, static_cast<char>(ch));
                    }
                    else
                    {
                        displayChar = "[byte:" + std::to_string(static_cast<int>(ch)) + "]";
                    }
                    break;
            }
            std::cout << displayChar << ": " << pair.second << std::endl;
        }
    }

    auto end = std::chrono::steady_clock::now();

    auto elapsed = std::chrono::duration<double, std::milli>(end - start);
    std::cout << "Elapsed time: " << std::fixed;
    std::cout.precision(3);
    std::cout << elapsed.count() << " ms\n";

    return 0;
}

サンプルをビルドして実行してベースラインを取得する

SPGO を適用する前に、textCount をビルドし、War、Peace (Project Gutenberg からダウンロードできます) などの大きなテキスト ファイルに対して実行して、実行速度を確認します。 この手順では、SPGO を使用して最適化する前のパフォーマンスを示します。

ビルド:

cl /EHsc /GL /O2 textCount.cpp

実行:

textCount.exe < warAndPeace.txt

次のような出力が表示されます。

=== TEXT STATISTICS ===
Lines: 66041
Words: 566333
Total Characters: 3227531

=== CHARACTER FREQUENCIES ===

Letters:
a: 202719
...

Elapsed time: 512.000 ms

Elapsed time 値を記録します。 結果の測定では、SPGO に最適化された時間と比較 します

/spgo を使用して textCount をビルドする

SPGO を有効にして textCount をビルドします。 この手順では、プロファイル データを収集するための基礎を築きます。

cl /EHsc /GL /O2 textCount.cpp /link /debug /spgo

ビルドが完了すると、次のようなメッセージが表示されます。

SPD textCount.spd not found, compiling without profile guided optimizations

このメッセージは、最初の /spgo ビルドに表示されます。 リンカーは SPD ファイルを作成しますが、まだ空であるため、SPGO の最適化はまだ適用されません。 バイナリを実行し、プロファイル データを収集して SPD に変換すると、このメッセージは表示されません。

フラグの説明:

フラグ Purpose
/EHsc C++ 例外処理を有効にする
/GL プログラム全体の最適化 - SPGO に必要です。 最終的な最適化を延期してリンク時間を設定し、モジュール間インライン化、コード レイアウト、およびデッド コード排除の決定を可能にします。
/O2 速度の最適化 — アグレッシブなインライン化、ループ最適化、デッド コードの削除、関連する変換を可能にします。
/link /debug /debugをリンカーに渡してデバッグ情報 (.pdb) を生成します。この情報は、xperf がプロファイリング サンプルをソース コードにマップするために使用します。
/spgo SPGO リンカー フラグ - バイナリに SPGO メタデータを埋め込み、実行可能ファイルと共に空の textCount.spd ファイルを作成します。

Note

/spgo はリンカー フラグです。 /link /spgo コマンドのclを使用してリンカーに渡します。

/spgo フラグでは、バイナリはまだ最適化されていません。 それをプロファイリングできるように準備します。 最適化は、SPD に実際のランタイム データが設定された後、 /spdin を使用した textCount の再構築 で行われます。

Note

特定の場所に SPD を書き込むには、省略可能な /spd:<path> リンカー フラグを追加します。 たとえば、 /link /debug /spgo /spd:.\profiles\textCount.spdと指定します。 このフラグを省略すると、SPD が .exeと共に作成されます。

プロファイリング方法を選択する

SPGO では、3 つのプロファイリング方法がサポートされています。 どの方法を使用するかは、ハードウェアによって異なります。

3 つのプロファイリング方法

メソッド サンプル品質 ハードウェア要件 最適な用途
LBR (最後の分岐レコード) 最高—直近に実行された分岐のシーケンスを記録し、オプティマイザーに各サンプルごとの豊富な制御フローデータを提供します Intel Haswell (2013) 以降。AMD Zen 4 (2022) 以降;ARM64 ARMv9.2-A (2020) 以降 最新のデスクトップ ハードウェア
PMC/IP モード (パフォーマンス監視カウンター/命令ポインター モード) 良い。 CPU のパフォーマンス監視ユニット (PMU) を使用して、コール スタックを含む命令ポインターのサンプルを取得し、Windows のイベント トレーシング (ETW) を通じて収集します PMU を備えた x64 または ARM64 CPU LBR がサポートされていないハードウェア
OS タイマー 基本 - タイマーベースのサンプル PMU パススルーのない x64 または ARM64 CPU、VM VM と古いハードウェア

PMC/IP モードでは、各ハードウェア割り込みによって、"割り込みが発生したときに CPU がアドレス 0x1A2B3C4Dにあった" というデータ ポイントが 1 つだけ与えられます。 LBR では、割り込みごとに、割り込みが発生する前に CPU が取った最後の 16 ~ 32 分岐のスタックが提供されます。 オプティマイザーは、より優れた制御フロー データを取得し、インライン化とレイアウトの決定を改善できます。

経路を検出

次の 2 つのコマンドを実行して、マシンがサポートするプロファイル パスを決定します。 これらのコマンドは、管理者権限のプロンプトを必要としません。

手順 1: LBR のサポートを確認します。 このテストは Intel/AMD/ARM64 で動作します。

administrator Visual Studio 開発者コマンド プロンプトから次を実行します。

xperf.exe -on PMC_PROFILE -pmcprofile TotalIssues -LastBranch PmcInterrupt -setProfInt TotalIssues 2560000
xperf -stop -d lbrtest.etl
xperf -tle -i lbrtest.etl -a dumper | findstr "LBR,  TimeStamp"
  • このコマンドで LBR, TimeStampを含む行が見つかると、マシンは LBR をサポートします。 LBR パスを使用します。
  • それ以外の場合は、手順 2 に進みます。

手順 2: PMC のサポートを確認する (LBR なし)

xperf.exe -pmcsources | findstr TotalIssues
  • このコマンドによって出力が生成される場合、マシンは PMC カウンターをサポートしますが、LBR はサポートしません。 PMC パスを使用します。
  • このコマンドで出力が生成されない場合は、 OS タイマー パスを使用します。

xperf を使用した PMU イベント収集の詳細については、「xperf を使用した ハードウェア PMU イベントの記録」を参照してください。

意思決定テーブル

LBR, TimeStamp出力 TotalIssues 出力 あなたのパス
空ではない (未チェック) LBR
空ではない PMC
OS タイマー
ARM64 プロセッサ N/A PMC (PMU が使用可能な場合) または OS タイマー

アプローチを選択する

検出結果に基づいて、LBR、PMC、または OS タイマー パスのどちらを使用するかを決定します。 各パスには、適切なプロファイル データを収集するための異なる xperf 開始パラメーターがあります。 ハードウェアの機能に一致するパスに従います。

パス:

  • LBR ユーザー (手順 1 で LBR が検出されました): LBR パスに移動します。
  • PMC ユーザー (手順 2 で InstructionRetired が検出されました): PMC パス (LBR なし) に移動します。
  • OS タイマー ユーザー (VM または PMU のないハードウェア): OS タイマー パスに移動します。

すべてのパスは、「 ワークロードを実行して xperf を停止する」で再び参加します。

このセクションのコマンドは、「プロファイリング 方法の選択」で指定したプロファイル パスによって異なります。 パスに一致するサブセクションを見つけ、 xperf 開始コマンドを実行し、続けて ワークロードを実行し、xperf を停止 してワークロードを実行し、xperf を停止します。

⚠️ 管理者として実行:xperf 管理者特権 (管理者) の開発者コマンド プロンプトが必要です。 標高がない場合、 xperf"failed to configure counters"を返します。

LBR パス

LBR コレクションを使用して xperf を開始します。

xperf -on LOADER+PROC_THREAD+PMC_PROFILE -MinBuffers 4096 -MaxBuffers 4096 -BufferSize 4096 -pmcprofile BranchInstructionRetired -LastBranch PmcInterrupt -setProfInt BranchInstructionRetired 16384

パラメーターの説明:

パラメーター Purpose
LOADER+PROC_THREAD+PMC_PROFILE カーネル プロバイダー: ローダー イベント (モジュール マッピング)、プロセス/スレッド イベント (実行コンテキスト)、PMC プロファイリング イベント
-MinBuffers 4096 -MaxBuffers 4096 -BufferSize 4096 『戦争と平和』を通しで実行してもサンプルが欠落しないようにするための大容量リングバッファ
-pmcprofile BranchInstructionRetired PMC イベント トリガー: N 番目に廃止されたブランチ命令ごとにサンプルを生成する
-LastBranch PmcInterrupt LBR ハードウェア記録を有効にします。各 PMC 割り込み時に、ハードウェアの最後の分岐レコード スタックをキャプチャします
-setProfInt BranchInstructionRetired 16384 サンプル間隔: 16,384 個の廃止されたブランチ命令ごとに割り込みを発生させます

xperf を開始した後、「 ワークロードの実行」に進み、xperf を停止します。

PMC パス (LBR なし)

PMC/IP モード コレクションを使用して xperf を開始します。

xperf -on LOADER+PROC_THREAD+PMC_PROFILE+PROFILE -MinBuffers 4096 -BufferSize 4096 -pmcprofile InstructionRetired -setProfInt InstructionRetired 16384 -stackwalk profile

パラメーターの説明:

パラメーター Purpose
LOADER+PROC_THREAD+PMC_PROFILE+PROFILE PMC イベントの PROFILE (CPU サンプリング) と PMC_PROFILE を追加します。いいえ -LastBranch
-pmcprofile InstructionRetired PMC イベント トリガー: 廃止された命令のサンプル (命令ポインター モード)
-setProfInt InstructionRetired 16384 16,384 個の廃止された命令ごとに割り込みを発生する
-stackwalk profile 各プロファイル割り込みで呼び出し履歴をキャプチャし、ブランチ シーケンスの代わりに呼び出しチェーン データを提供する

LBR と比較すると、-LastBranch フラグはありません。InstructionRetiredではなくBranchInstructionRetiredが使用されます。 結果は、分岐シーケンスではなく、コールスタックを伴う命令ポインターのサンプルです。 このパスは、オプティマイザーに有効なデータを引き続き提供しますが、少しリッチではありません。

xperfを開始したら、「ワークロードの実行」に進み、xperf を停止します。

OS タイマーの経路

OS タイマー ベースのサンプリングを使用して xperf を開始します。

xperf -on LOADER+PROC_THREAD+PROFILE -MinBuffers 4096 -BufferSize 4096 -setProfInt Timer 1221 -stackwalk profile

パラメーターの説明:

パラメーター Purpose
LOADER+PROC_THREAD+PROFILE PMC イベントはありません。OS タイマー割り込みによる CPU サンプリングのみ
-setProfInt Timer 1221 1,221 タイマー ティック (約 1 kHz) ごとに OS タイマー割り込みで起動します
-stackwalk profile タイマー割り込みごとに呼び出し履歴をキャプチャする

LBR および PMC と比較して、このメソッドはハードウェア パフォーマンス カウンターを使用しません。 OS タイマーは、CPU アクティビティに関係なく、ほぼ固定された時間間隔で起動します。 サンプルはホット コードとの関連性は低くなりますが、オプティマイザーに役立つ制御フロー データを提供します。

ワークロードを実行して xperf を停止する (すべてのパス)

xperfを実行した状態で、War and Peace を対象に textCount を実行します。

textCount.exe < warAndPeace.txt

textCountが完了したら、xperfを停止し、トレース ファイルを書き込みます。 プロファイリング中に他のプロセスを実行すると、サンプル品質が希釈されます。 最適な結果を得るには、ワークロードを実行する前に不要なアプリケーションを閉じます。

xperf -stop -d textCount.etl

xperfを停止した後 (etl ファイルの書き込みに時間がかかる場合があります)、textCount.etlが現在のディレクトリに作成されたことを確認します。

ETLファイルをSPTに変換する

この手順は、3 つのプロファイル パスすべてで同じです。

SPTAggregate.exeを実行して生 ETL トレースを処理し、SPT プロファイル ファイルを作成します。

SPTAggregate.exe /binary textCount.exe /etl textCount.etl textCount.spt

パラメーターの説明:

パラメーター Purpose
/binary textCount.exe サンプルを抽出するバイナリ。 ETL には、プロファイリング中に実行されたすべてのプロセスのサンプルが含まれている場合があります
/etl textCount.etl 入力 ETL トレース ファイル
textCount.spt SPT プロファイル ファイルの出力

SPTAggregate は、収集したサンプルの数を示す概要を出力します。 この概要は、プロファイリングが機能したことを示す最初の確認です。

SPTAggregateからの出力を、実行したパスと照合します。

  • LBR パス: 使用された LBR サンプル数が 0 ではないことを確認してください。
  • PMC パス: 0 以外の PMC またはスタック サンプル数を探します。
  • OS タイマーのパス: 0 以外の使用済みスタック サンプル数を探してください。

すべてのカウントが 0 の場合は、続行する前に トラブルシューティングを 参照してください。

SPTファイルをSPDに変換する

パス:

PMC と OS の両方のタイマー パスで /mode:IP が使用されます。これは、どちらも命令ポインター のサンプルを生成するためです。

次のステップはプロファイリング パスに応じて分岐します。具体的には、SPDConvert.exe に渡される /mode フラグに基づきます。

LBR モード

SPDConvert.exe /mode:LBR textCount.spd textCount.spt

/mode:LBR は、SPT を LBR 分岐シーケンス データを含むものとして解釈するように SPDConvert に指示します。

IP モード (PMC および OS タイマー)

PMC と OS タイマーの両方で命令ポインターのサンプルが生成されるため、どちらも同じ変換コマンドを使用します。

SPDConvert.exe /mode:IP textCount.spd textCount.spt

/mode:IP は、SPT を命令ポインターサンプルを含むものとして解釈するように SPDConvert に指示します。

Warning

データ型に間違ったモードを使用すると、空または形式が正しくない SPD が生成される可能性があります。 LBR を使用してプロファイリングを行った場合は、 /mode:LBRを使用します。 PMC または OS タイマーでプロファイリングを行った場合は、 /mode:IPを使用します。 [SPTAggregate] の概要出力には、収集されたサンプルの種類が表示され、使用する正しいモードが確認されます。

SPDConvertを実行した後、textCount.spdが現在のディレクトリに作成 (または更新) されたことを確認します。

SPDConvert 出力の解釈

このコマンド SPDConvert textCount.spd textCount.spt 、前後のブロック カバレッジの概要を出力します。次に例を示します。

Block coverage (before) : 33.90% ( 4507/ 13294)
Block coverage (after)  : 45.64% ( 6067/ 13294)

この概要は、プロファイル データが関連付けられているバイナリのコード ブロックの割合を示しています。 パーセンテージが高いほど良いです。 70% を超えるカバレッジは優れていますが、40% 未満のカバレッジでは最適化の有効性が制限される可能性があります。 カバレッジが低い場合は、プロファイリング ワークロードを長く実行するか、複数の SPT ファイルを異なるワークロードで個別の実行から結合します。 たとえば、複数のテキスト ファイルに対して textCount を実行して、異なるコード パスを実行できます。

次のように、 SPDConvert からの警告が表示される場合があります。

Compiler may be conservative on some hot functions due to sparse sample coverage.
SPGO is estimated to optimize better if sample density is increased to 5.4x of current level.
Sample density can be increased by sampling for longer period, or increasing sample rate.

この警告は、すべてのホット関数をオプティマイザーが確実に最適化するのに十分なサンプルがプロファイリング実行によって収集されなかったことを意味します。 SPD は引き続き使用できますが、次の方法で結果を向上させることができます。

  • ワークロードの実行時間を長くする (1 分ではなく 5 分以上など) か、異なるワークロードを使用する。
  • サンプリング レートを上げるには、-setProfInt コマンドのxperf値を下げます。 トレードオフは、この変更により、より大きな ETL ファイルが生成され、処理に時間がかかるということです。
  • 複数の SPT ファイルを個別のプロファイリング実行から結合するには、それらをすべて SPDConvert に渡します。

SPTファイルはバイナリ形式です。 その内容を調べるには、 SPTDump.exe textCount.sptを実行できます。 同様に、 PTDump.exe textCount.spt では、 SPDConvertの実行後にコンパイルされたプロファイル データが表示されます。 どちらのツールも、続行する前に 0 以外のサンプルを確認するのに役立ちます。

/spdin を使用して textCount を再構築する

設定された SPD ファイルを使用して、 textCount を再構築します。 リンカーはプロファイル データを読み取り、SPGO 最適化を適用します。

この手順は、3 つのプロファイル パスすべてで同じです。

cl /EHsc /GL /O2 textCount.cpp /link /debug /spgo /spdin:textCount.spd

新しいフラグ(/spgo を使用して textCount をビルドと比較して):

フラグ Purpose
/spdin:textCount.spd 最適化のためにリンカーに SPD プロファイル データを提供する

このコマンドには引き続き /spgoが含まれます。 最適化されたバイナリと共に新しい SPD ファイルが生成されます。これは、後続のプロファイリング イテレーションの開始点として使用できます。

Warning

SPD ファイルは、プロファイル対象の正確なバイナリに関連付けられています。 textCountせずに/spdinをリビルドする場合、または変更されたソースからリビルドする場合は、新しい SPD ファイルを生成する必要があります。 既存の GUID は新しいバイナリの GUID と一致せず、リンカーはそれを使用しません。

/spdinを使用したリビルド後、リンカーはプロファイル データを使用してコードの最適化量に関する統計情報を出力します。 例えば次が挙げられます。

221 of 221 (100.00%) profiled functions will be compiled for speed
201 of 1383 inline instances were from dead/cold paths
474 of 474 profiled functions (100.0%) were optimized using profile data
202738780 of 202738780 instructions (100.0%) were optimized using profile data

割合が高いということは、SPD がバイナリを適切にカバーするということです。 割合が低い場合 (たとえば、90%未満)、プロファイリング ワークロードでバイナリが十分に実行されなかったか、プロファイルが収集されてからバイナリが大幅に変更されています。 どちらの場合も、現在のバイナリを対象に再度プロファイリングしてください。

プロファイル データに対する SPGO の機能

SPGO は、収集されたサンプル データを使用して、プログラムの制御フロー グラフ内の各ブロックとエッジの数を設定します。 次のようなドライブの最適化がカウントされます。

  • プロファイルガイド付きインライン化: コールド パスのインライン化によるコードの肥大化を回避しながら、ホット コール サイトを積極的にインライン化します。
  • ホット/コールド コードの分離: 実行頻度の低いコードをバイナリのセクションに移動し、命令キャッシュの使用率とページング動作を向上します。
  • 関数レイアウト: 互いに頻繁に呼び出し合う関数をバイナリ内に配置し、ページ フォールトを減らし、局所性を向上させます。 最適化された関数は、バイナリ内の高アフィニティ COFF グループに編成されます。
  • サイズ/速度の選択: ホット関数は速度重視で、コールド関数はサイズ重視でコンパイルします。 プロファイル ヒットが観察されないルーチンは、速度ではなくサイズ用にコンパイルされ、インライン化やループ アンロールなどの最適化がこれらのコールド パスで制限される場合があります。
  • 投機的デバーチャライゼーション: サンプリングによって間接呼び出しが一貫して同じ関数を呼び出していることが判明した場合、SPGO はその関数が呼び出し先であると推定してインライン化し、まれなケースに備えてフォールバックを用意できます。

結果を測定する

もう一度 textCount 実行し、経過時間を比較します。

textCount.exe < warAndPeace.txt

構成ごとに複数の実行を収集し、中央値を使用します。 OS のスケジュール設定とシステム ノイズによって個々の測定値が歪む可能性があるため、1 回の実行は信頼できません。

Build 代表的な経過時間
ベースライン (cl /EHsc /O2) (あなたの測定)
/spgo ビルド (プロファイル データがまだない) (ベースラインに近い必要があります)
SPGO最適化 (/spdin) (改善を示す必要があります)

1つの試験において、LBR法を用いたSPGOは経過時間の約7% 減少を提供した。 SPGO の向上は、プロファイリング ワークロードが一般的な実行をどの程度適切に表しているかによって異なるため、結果は独自のプロジェクトによって異なる場合があります。 分岐が多いコードベースが大きいと、5 ~ 10% の範囲内でより多くの改善が見受けられます。 プロファイリング方法は、最適化の品質に影響します。 LBR は通常、PMC よりも優れた結果を生成し、OS タイマーよりも優れた結果を生成します。 OS タイマー パスを使用している場合は、より小さな利益が得られます。

このチュートリアルで実行した LBR パスは、実稼働データベース ライブラリである SQLite プロジェクトに適用されました。 SPGO 最適化 SQLite バイナリは、約 7% の改善を示しました。

独自のプロジェクトに SPGO を適用する

このチェックリストを使用して、独自の C または C++ アプリケーションに SPGO を適用します。

  1. 既存のリリース ビルド コマンドに /link /spgo を追加します。 ビルド スクリプトまたはプロジェクト ファイルを変更します。

    cl /EHsc /GL /O2 myapp.cpp /link /spgo
    
  2. 代表的なワークロードを選択します。 アプリケーションのホット パスを実行する実際の使用シナリオを選択します。 運用環境に似たデータを使用します。 主要なプロファイリング ワークロードとして、コード カバレッジ テスト (パフォーマンスのボトルネックを強調しない)、一般的でないエラー パス、スタートアップフェーズとシャットダウン フェーズ、および非推奨のコード パスは避けてください。 このワークロードは、オプティマイザーにフィードするプロファイルを駆動します。

  3. 検出されたパスを使用して xperf を実行します。 プロファイル方法の選択 (LBR、PMC、または OS タイマー) で指定したパスを使用します。 xperfを開始し、ワークロードを 1 回実行し、xperfを停止して ETL ファイルをキャプチャします。

  4. PMC または OS タイマー パスの場合は、 正しい /mode フラグを指定して SPTAggregate と SPDConvert を実行します。 ETL を SPT に変換し、SPD に変換します。 LBR データには /mode:LBR を使用します。PMC または OS タイマー データには /mode:IP を使用します。

  5. /spdin:<your-spd-path>を使用して再構築します。 設定された SPD を使用してアプリケーションをコンパイルします。

    cl /EHsc /GL /O2 yourApp.cpp /link /spgo /spdin:yourApp.spd
    
  6. 前と後を測定します。 最適化されていないバイナリと SPGO 最適化バイナリの両方を使用してワークロードを実行します。 構成ごとに 複数の実行の中央値 を収集します。 1 回の実行はベンチマークでは信頼できません。

  7. .spd ファイルをソース管理に格納します。 ソース コードと共にソース管理システムに .spd ファイルを確認します。

  8. 開発者リリース ビルドで SPGO を有効にします。 チームのリリース ビルドで、運用環境と同じ SPGO 最適化バイナリを使用するようにします。 これは、パフォーマンスの低下を早期にキャッチするのに役立ちます。

  9. デバッグ ビルドで SPGO を無効にします。

  10. リンカーのプロファイルの完全性の統計情報を確認します。 /spgoを使用した各ビルドの後、プロファイル データを使用して最適化されたプロファイリング関数の割合に注意してください。 これが大幅に低下した場合 (90%未満)、現在のバイナリを再現します。 コードの変更が蓄積され、SPD が古くなる可能性があります。

xperf を使用する代わりの方法

プロファイル データを収集するもう 1 つの方法は、Windows Performance Recorder (WPR) などのサンプリング プロファイラーを使用することです。 WPR は、Windows 10 以降に既定でインストールされます。 xperfと同様のデータが収集されます。 呼び出し履歴を使用して CPU サンプルを収集するように WPR を構成し、SPTAggregate ETL などのSPDConvertおよびxperfで処理できる ETL ファイルにデータをエクスポートできます。 WPR を使用してプロファイル データを収集する例を次に示します。

wpr -start CPU.light -filemode
textCount.exe < warAndPeace.txt
wpr -stop spgo_data.etl

WPR の使用方法の詳細については、Windows Performance Recorder の使用を参照してください。

SPD ディストリビューション

次のようにすることができます。

  • .spd ファイルをソース コードと一緒にソース コントロールに直接チェックインしてください。
  • .spd ファイルをチームメンバーと共有すると、再プロファイリングを行わずに SPGO 最適化を有効にしてビルドできます。
  • バイナリをバージョン管理された成果物 (NuGet パッケージなど) として .spd ファイルにパッケージ化し、どのバージョンがどのバイナリに対応するかを記録します。
  • プロファイリング ワークフローを繰り返して、 .spd ファイルをいつでも再生成します。

SPD は、それが構築された正確なバイナリに結び付けられます。 コードが大幅に変更された後、再プロファイルして新しい SPD を生成します。 /spdinビルド中に、コンパイラによって新しい.spd ファイルも生成されます。 この新しい SPD をビルド成果物として保存します。これは、次のプロファイリング イテレーションの開始点です。

ビルド間でのSPD情報の再利用

SPGO の "繰り越し" の概念を使用すると、すべてのシナリオを最初から再びプロファイリングすることなく、既存のプロファイル情報を失うことなく、既存の SPD ファイルにプロファイリング データを追加できます。 また、古いプロファイル データに与える重みの量を調整することもできます。 この柔軟性は、時間の経過とともに動作が変化する可能性があり、以前のシナリオの実行からプロファイリング情報を完全に失いたくない場合に役立ちます。 たとえば、その DLL を呼び出すアプリケーションの進化に伴い、DLL では異なる API が呼び出されるようになることがあります。 従来の振る舞いに基づく最適化は維持しつつ、現在は時折異なる振る舞いをすることで生まれる最適化の機会も取り入れたい。 古いデータと新しいデータを混在させることで、プロファイルを徐々に進化させることができます。

新しい SPT ファイルで SPDConvert を実行する場合は、既存の SPD ファイルの名前を渡します。 次に、/retire:N オプションを使用して、新しい SPT ファイルを追加するときに SPDConvert が古いプロファイル データをどの程度強く弱めるかを制御します。

  • 既定 (/retire:8) では、新しいデータの重みが大きくなります。
  • /retire:0 を使用すると、すべての実行に同じ重みを適用できます。
  • /retire:16を使用して、最新のデータカウントのみを許可します。

Troubleshooting

問題を見つける:

LBR パスの問題点

問題 考えられる原因 修正
SPTAggregate 出力内の LBR サンプル数がゼロ CPU で LBR がサポートされていないか、VM が LBR を公開していない [パスの検出] から 検出コマンドを実行します。 Hyper-V VM の場合は、ホストで Set-VMProcessor MyVMName -Perfmon @("pmu", "lbr") を実行します。 LBR が使用できない場合は、PMC または OS タイマー パスに切り替えます。
プロセッサは LBR をサポートしますが、 SPTAggregate は 0 個の LBR サンプルを示しています perfcore.ini DLL の登録が不完全 perfcore.ini の構成perfcore.ini の設定を完了してください。 perf_lbr.dllが登録されていることを確認します。
SPDConvert が失敗するか、空の SPD が生成される /mode フラグが正しくないか、SPT に IP モードのサンプルのみが含まれている SPTAggregate 出力に LBR サンプルが表示されていることを確認してください。 出力に IP モードのサンプルのみが表示される場合は、 /mode:IPに切り替えます。

PMC パスの問題

問題 考えられる原因 修正
SPTAggregate出力のゼロ PMC サンプル perfcore.ini DLL の登録が正しくありません Configure perfcore.iniperfcore.ini のセットアップを完了します。 perf_spt.dllが登録されていることを確認します。 この DLL がないと、 xperf はエラー メッセージなしで 0 個の PMC サンプルを生成します。
xperf.exe -pmcsourcesを実行して、CPU で使用可能なパフォーマンス カウンター ソースの一覧を表示します。 SPT_OP_RETIRE_INSTRSPT_OP_RETIRE_BR_INSTRSPT_OP_ETW_INSTRなどのエントリが表示されない場合は、perfcore.iniでの DLL の登録が不完全であるか、CPU が PMC をサポートしていない可能性があります。 DLL の登録を解決できない場合は、代わりに OS タイマー パスを試してください。
findstr InstructionRetired は出力を返しますが、 xperf はサンプルを生成しません VM による PMC カウンターのマスキング VM で実行されているかどうかを確認します。 Set-VMProcessor で Hyper-V で PMU を有効にするか、OS タイマー パスに切り替えます。
SPDConvert PMC パスで失敗する IP 専用 SPT での /mode:LBR の使用 /mode:IPに切り替えます。

OSタイマーのパスの問題

問題 考えられる原因 修正
予想よりも改善が少ない 予期される - OS タイマーの忠実度が低い これは正常です。 オプティマイザーがタイマーサンプルから得られる分岐フロー情報は、LBR または PMC から得られるものより少なくなります。 性能向上幅は小さいです。 ハードウェアでサポートされている場合は、PMC または LBR へのアップグレードを検討してください。
ゼロ タイマーのサンプル xperf が管理者権限のプロンプトで実行されなかったか、PROFILE プロバイダーが見つかりません 管理者として実行を確認します。 -stackwalk profilexperf コマンドに指定されたことを確認します。

共通の問題(すべてのパス)

問題 考えられる原因 修正
"failed to configure counters" エラー xperf 管理者として実行されていない コマンド プロンプトを管理者として再起動します ([ 管理者 として実行] > 右クリックします)。 xperf では、ハードウェア パフォーマンス カウンターを構成するために昇格された特権が必要です。
xperf 見つかりません xperf.exe は PATH に含まれていません WINDOWS ADK がインストールされていることを確認します。 C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\ を確認します。 そのディレクトリを PATH に追加するか、そこから直接 xperf を実行します。
textCount.etl 作成されていません xperf がサイレントモードで失敗しました 管理者として実行を確認します。 xperf start コマンドを再実行し、エラー出力を確認します。
SPTAggregate "バイナリが見つかりません" で失敗する textCount.exe 現在のディレクトリまたは間違ったパスにありません textCount.exeと同じディレクトリに含まれているか、/binary パラメーターへの完全なパスを指定します。
SPD ファイルが作成されない SPDConvert 失敗 しました textCount.sptサイズが 0 以外であることを確認します。 SPTDump.exe textCount.sptを実行して内容を調べます。
/spdin ビルドしても改善されない SPD とバイナリの GUID/age の不一致 SPD は別の textCount.exeから構築されました。 現在のビルドをもう一度プロファイリングして、新しい SPD を生成します。
MSVC のバージョン エラー /spgo v14.51 より前の MSVC ツールセット Visual Studio Installer を開き、>個別のコンポーネント> で MSVC v14.51 以降をインストールします。 開発者コマンド プロンプトをもう一度開きます。

次のステップ

このチュートリアルを完了したら、これらの機能を調べて SPGO からさらに多くを取得します。

  • プロファイル ブレンド: 複数のワークロードを実行し、各実行から SPT ファイルを蓄積し、それらのすべてを SPDConvertに渡します。 ブレンドされた SPD は、実際の使用パターンの全範囲を反映し、単一シナリオ プロファイルよりも優れた最適化を生成します。 /retire:N オプションを使用して、新しい SPT ファイルを追加するときに、SPDConvert が古いプロファイル データの重要度をどの程度下げるかを制御します。 既定 (/retire:8) では、新しいデータの重みが大きくなります。 /retire:0を使用して、すべての実行に等しい重みを与えます。/retire:16を使用して、最新のデータカウントのみを許可します。
  • 最適な結果は、主要なシナリオに重点を置くベンチマークや実際のデータ (使用可能な場合) など、複数のソースからのプロファイルをブレンドすることです。 すべてのソースから SPDConvert に SPT ファイルを渡します。 引数リスト内で SPT ファイルを繰り返して指定すると、そのファイルにより大きな重みを与えることができます(たとえば、SPDConvert myapp.spd critical.spt critical.spt common.sptcommon.spt の 2 倍の重みになります)。
  • 反復的な最適化:/spdinを使用した各再構築では、新しい SPD が生成されます。 実行、プロファイル、再構築サイクルを繰り返すことができます。 後の反復ではリターンが減少する可能性がありますが、2 番目のパスでは最初に見逃したパターンをキャプチャすることがあります。
  • コードの変更: ソースが大幅に変更された後、プロファイル データを再収集します。 既存の SPD は、プロファイリング対象のバイナリに関連付けられています。 実質的に再構築されたバイナリと一致しません。
  • プロファイルの鮮度: リンカーは、各 /spdin ビルド後にプロファイル データを使用して最適化されたプロファイリング関数の割合を報告します。 この割合が大幅に低下した場合は、コードがプロファイルから逸脱したことが示されます。 現在のバイナリを再度プロファイリングしてください。