How to Optimize Your Application

nVidiaGDC 2004で行ったプレゼンから『Optimization for DirectX 9 Graphics』を気まぐれに訳してみる.意訳が多いので注意.
http://developer.nvidia.com/object/gdc_2004_presentations.html
今回紹介するのは第2章『How to Optimize Your Application』.

Chapter 2. How to Optimize Your Application

本章ではグラフィックスアプリケーションのパフォーマンスボトルネックを見つけて除去するため,一般的にどうやっていけばいいかを調べていきます.

2.1. Making Accurate Measurements

沢山の便利なツールは,テストされ信頼のおけるパフォーマンスの指標を提供し,パフォーマンスを測定することが出来ます.NVPerfHUDの黄色のライン(Chapter 5参照)はフレームあたり何ミリ秒かったかを測ったもので,現在のフレームレートを示します.

有効なパフォーマンスの比較を可能にするために:

  • アプリケーションがクリーンな環境で動いていることを確かめよ.
    例えば,MicrosoftのDirectX Debug Runtime上でアプリケーションを動かす場合,ランタイムはエラーやWarningを一切出力すべきではありません.
  • テスト環境の妥当性を確認せよ.
    実行しているのがリリースバージョンのアプリケーションとDLLであることを確かめます.同様に最新リリースバージョンのDirectX Runtimeであることも確かめます.
  • 全てのソフトウェアで(デバッグバージョンではなく)リリースバージョンを使用せよ.
  • ディスプレイ設定が正しいことを確認せよ.
    一般的にこれはデフォルト値になっていることを意味します.異方性フィルタとアンチエイリアスの設定は特にパフォーマンスに影響を与えます.*1
  • ドライバの垂直同期設定を無効にせよ.
    これはモニターのリフレッシュレートがフレームレートを制限しないことを保証します.

2.2. Finding the Bottleneck

2.2.1 Understanding Bottlenecks

仮に,ある状況でパフォーマンスが芳しくないと分かったとします.これから我々はパフォーマンスボトルネックを見つけ出す必要があります.ボトルネックは一般にシーンの内容に依存して変わります.事態が複雑になることに,しばしばボトルネックは1フレームごとに変化します.従って,「ボトルネックを見つけること」の真の意味は「このシナリオにおいて最も大きな制限を見つけよう」ということです.このボトルネックを取り除くことで最も大きなパフォーマンス改善を得られるのです

理想的な場合,ボトルネックは何1つ存在しません――CPUもAGPバス*2GPUパイプラインも等しく働くでしょう.しかしこれは達成不可能なことです――実際は常に何かがパフォーマンスを制限します.問題はそのパフォーマンスが許容可能かどうかということです.もしそれが許容可能であれば他に何もする必要はありません.

ボトルネックはGPU上にあるかもしれませんしGPU上にあるかもしれません.NVPerfHUDの緑のライン(see Chapter 5)はGPUが1フレーム中に何ミリ秒idleだったかを示しています.もしGPUが1フレームあたりたった1ミリ秒でもidleだった場合,アプリケーションが少なくとも部分的にはCPU-limitedであるしるしです.もしGPUが1フレームの大きな割合でidleであったなら,あるいは全てのフレームについてGPUが1ミリ秒でもidleでかつアプリケーションがCPUとGPUの同期を行っていないなら,CPUが最大のボトルネックです.単にGPUのパフォーマンスを向上させても,GPUのidle timeが増えるだけです.

2.2.2 Basic Tests

あなたのアプリケーションのボトルネックを特定するために,いくつかのシンプルなテストを行うことが出来ます.これらを試すのに特別なツールやドライバは必要ありません.これらは最も簡単な始め方です.

  • ファイルアクセスを除去せよ.
    ハードディスクへのアクセスは全てフレームレートを悪化させます.これを検出するのは容易です――コンピュータの"ハードディスク使用中"のランプを見ることです.ハードディスクアクセスはメモリスワップによっても引き起こされることを覚えておいてください.特にあなたのアプリケーションが大量のメモリを使うのなら.
  • 同じGPUを異なる速度のCPU上で動作させてみよ.
    CPUスピードを調整できるBIOSを探してみるのは役に立ちます.何故ならたった1つのシステムでテストできるからです.もしフレームレートがCPU速度に依存して変化したらアプリケーションはCPU-limitedです.
  • GPUのコアクロックを下げてみよ.
    これを行うCoolbitsやPowerstripのようなツールが一般的に入手可能です*3.もしコアクロックを下げることでパフォーマンスが低下するなら,アプリケーションは頂点シェーダ,ラスタライズあるいはフラグメントシェーダによる制限を受けています.(すなわちShader-limitedです)
  • GPUのメモリクロックを下げてみよ.
    これを行うCoolbitsやPowerstripのようなツールが一般的に入手可能です.もしメモリクロックを下げることでパフォーマンスが低下するなら,アプリケーションはテクスチャまたはフレームバッファのバンド幅による制限を受けています.(GPU bandwidth-limited)
2.2.3 Using a Null Hardware Driver

CPUボトルネックを検証するために,特別なドライバ上でアプリケーションを実行します.これはNull Hardware (Null HW)と呼ばれるドライバです.このドライバは通常のドライバのように働きます(通常のドライバと同一のコードパスを通ります)が,異なるのはGPUに一切の処理を渡さないことです.従ってNull HWドライバはある無限に高速なGPUのエミュレートを行います.もしNull HWドライバのパフォーマンスが通常のドライバと同じであれば,アプリケーションは完全にCPU-boundです

2.3. Bottleneck: CPU

もしアプリケーションがCPU-boundであれば,何がCPU時間を消費しているかを特定するためにプロファイリングを使用します.以下に挙げるモジュールが典型的に多量のCPU時間を使用しています.

  • Application (the executable as well as related DLLs)
  • Driver (nv4disp.dll)
  • Runtime (d3d9.dll)
  • Hardware abstraction layer (hal32.dll)

この段階の目的はCPUのオーバーヘッドを減らすことなので,CPUはいまやボトルネックではありません.CPU時間がどこで消費されるかは相対的に低い重要性しか持ちません.代わりに,可能な箇所全てでCPUによる計算を節約することに注目します.いつものアドバイスが当てはまります:ヒープホール最適化を通したアルゴリズム的な改善を選択せよ.そしてもちろん,最大のパフォーマンスゲインを得るために最大のCPU消費箇所を探します.

次にアプリケーションのコードに穴を空け,コードモジュールを小さくできないかあるいは取り除けないかを調べます.もしアプリケーションがhal32.dllやd3d9.dllでCPUを大量に消費していたなら,それはAPIの使いすぎであることを示しています.ドライバがCPUを大量に消費しているとしたら,ドライバの呼び出し回数を減らすことは可能でしょうか? ドライバの呼び出しを減らすのにはバッチサイズを改善することが有用です.バッチ処理に関する詳しい情報は以下で入手できます.
http://developer.nvidia.com/docs/IO/4449/SUPP/BatchBatchBatch.ppt

NVPerfHUD はドライバオーバーヘッドを特定する助けとなります.それはフレームあたりドライバで消費された時間の合計(赤い線でプロットされます)とフレームあたりのバッチの数をグラフ表示します.
CPU-bound時にチェックすべきその他の点:

  • アプリケーションはリソースをロックしていないか? 例えばフレームバッファやテクスチャを.
    リソースをロックすることでCPUとGPUシリアライズできてしまいます.実際,GPUがロックを返す準備が出来るまでCPUは停止します.そのためCPUは能動的な待機を行いアプリケーションのコードを処理するために使えません.従ってロックを行うことはCPUオーバーヘッドに繋がります.
  • アプリケーションはGPUをかばってCPUを使っていないか?
    少数の三角形の集まりを除去することはCPUの増やしてCPUの仕事を減らします.しかしCPUは既にidleなのです! CPU-bound時にはCPU側の最適化を取除くことで実際にパフォーマンスが改善します.
  • CPUの仕事をGPUに任せられないか考慮してみよ.
    あなたのアルゴリズムを再構成してGPUの頂点シェーダやピクセルシェーダに適したものに出来ませんか?
  • シェーダーを使ってバッチサイズを増やしドライバオーバーヘッドを削減せよ.
    例えば2つのマテリアルを1つのシェーダに合体させ単一のバッチでジオメトリを描画することが出来ます.

2.4. Bottleneck: GPU

GPUは深いパイプラインを持ったアーキテクチャです.もしGPUがボトルネックなのであれば,最大のボトルネックがパイプラインのどのステージなのかを見つけ出す必要があります.グラフィックスパイプラインの様々なステージの外観については,以下を参照してください.
http://developer.nvidia.com/docs/IO/4449/SUPP/GDC2003_PipelinePerformance.ppt

最もシンプルなツールはNVPerfHUDSettings.exeアプリケーションです(これはChapter 5でさらに説明します).これにより様々なGPUやドライバの機能をオンまたはオフに設定することができます.例えば,mipmap LOD biasを12に設定し,全てのテクスチャを1×1にすることが出来ます*4.もしパフォーマンスが大きく向上したのであれば,テクスチャキャッシュのミスヒットがボトルネックです.NVPerfHUDSettingsは同様にz-cullを制御したり,全てのピクセルシェーダを1サイクルで動作させる(別名white shaders)*5ことができます.

あなたのアプリケーションにとってGPUがボトルネックであると断定するなら,Chapter 3で紹介するtipsを用いてパフォーマンスを向上させてください.

*1:本来FSAAや異方性フィルタなどの設定はアプリケーション側からも設定可能なのだが,ドライバの設定でそれらをオーバーライドできるのが一般的である.恐らくこの機能をデフォルトに戻しておけと言っているのだろう.一方,GDIによるフォントレンダリングを行っている場合,フォント描画に関するアンチエイリアス設定がパフォーマンスに影響を及ぼすかもしれない.例えばClearTypeがパフォーマンスに影響を及ぼしているとするベンチマークが@ITにある

*2:AGPのPはPortなのだが……ポートバス……

*3:特別なツールは要らないって言ったのに……

*4:Mipmapテクスチャはオリジナルサイズとは別にその縮小版も事前に作っておき,描画時に使用するサイズを決定するという仕組みである.このサイズ選択に「通常より12段階小さなものを使え」というバイアスを設定することで,Mipmapテクスチャに関しては事実上全てが1×1のサーフェイスを描画に使用することになる

*5:これは常に白色を出力するという意味だろうか?