ID3D11QueryでGPUパフォーマンス測定

投稿日:

GPUが描画したポリゴン数や処理した頂点数などをID3D11Queryを使用して測定してみます。並列動作しているGPUの処理結果を取得するため、結果取得にちょっとした工夫が必要になります。単純な取得ではCPUとGPUの並列動作を止めてしまい、プログラムのパフォーマンスが悪化します。

ID3D11Queryの使い方

単純な測定方法です。GPUは並列動作しているため、結果を取得するためにはGPUの処理終了を待つ必要があります。待つ間CPUが停止してしまうため、処理時間が増加し、プログラムの性能が悪化します。状況によって変わりますが、検証用のプログラムでは30%ほど増加しました。

// グローバル変数など
ID3D11Query* pQuery;

//-------------------------------
//作成
D3D11_QUERY_DESC query_desc;
query_desc.MiscFlags = 0;
// このenum値で測定するものを決める
query_desc.Query = D3D11_QUERY_PIPELINE_STATISTICS;//頂点数やポリゴン数などの統計値
Device->CreateQuery(&query_desc, &pQuery);

//-------------------------------
// 測定 Begin~End間の測定
DevContext->Begin(pQuery);

    // 測定対象 描画処理など

DevContext->End(pQuery);

//-------------------------------
// 取得
// D3D11_QUERY_DESC::Query=D3D11_QUERY_PIPELINE_STATISTICS
// の時はD3D11_QUERY_DATA_PIPELINE_STATISTICSで結果受け取り
D3D11_QUERY_DATA_PIPELINE_STATISTICS query_data;
while(1){
    HRESULT hr = DevContext->GetData( pQuery, &query_data, sizeof(query_data),0);
    if(hr == S_OK)break;
    if(hr != S_FALSE)break;//エラー
    // hr==S_FALSEの時、GPU処理が終了していない
    // 結果取得のためにGPU終了(S_OK)になるまで待つ
}
//query_dataに測定結果

CPUを停止させない測定方法

スレッドを使わずに対処する方法です。測定結果の取得を遅延させて、GPU処理終了待ちが発生しないようにします。結果取得が数フレーム遅れるため厳密な測定はできません。

// グローバル変数など
const unsigned int PerfQueryNum = 5;// 遅延フレーム数
unsigned int idxPerfQuery = 0;
ID3D11Query* pPerfQuery[PerfQueryNum];

// 作成 同じものを複数
D3D11_QUERY_DESC query_desc;
query_desc.MiscFlags = 0;
query_desc.Query = D3D11_QUERY_PIPELINE_STATISTICS;
for( auto& q : pPerfQuery){
    hr = renctx.pDev->CreateQuery(&query_desc, &q);
}

// 測定
DevContext->Begin(pPerfQuery[idxPerfQuery]);
   // 測定対象
DevContext->End(pPerfQuery[idxPerfQuery]);

// 取得
D3D11_QUERY_DATA_PIPELINE_STATISTICS query_data;
zgU32 wait_cnt = 0;
while(1){
    // PerfQueryNumフレーム前の結果を取得
    // 時間経過によりGPU処理終了(の可能性が高い)
    unsigned int idx = (idxPerfQuery+1)%PerfQueryNum;
    HRESULT hr = DevContext->GetData( pPerfQuery[idx], &query_data, sizeof(query_data),0);
    if(hr == S_OK)break;
    if(hr != S_FALSE)break;//エラー
    ++wait_cnt;
}
// query_dataにPerfQueryNumフレーム前の測定結果
// wait_cnt>0ならGPU待ち発生、PerfQueryNumを大きく

// 結果取得を遅延させるためID3D11Queryをリングバッファに
idxPerfQuery = (idxPerfQuery+1)%PerfQueryNum;

ゲームプログラムであればPerfQueryNum=3ぐらい、というよりこれが3以上必要になるということはCPU(操作)とGPU(画像)のずれが大幅に発生、キー入力の反応が遅れすぎて操作性が悪くなります。CPUとGPUの同期にはIDXGISwapChain1::PresentやID3D11Query(D3D11_QUERY_EVENTで作成)などを使います。