ハードウェア・アクセラレーションのパフォーマンス最適化のポイント
メディア処理において、ハードウェア・アクセラレーションにより最大のパフォーマンスを引き出すためのポイントは、アクセラレーター・ハードウェアの利用率を可能な限り 100% に近い状態を保ち、フルに活用することである。前回までの考察を踏まえ、これをエンコーダー・アクセラレーションの場合にあてはめると、以下の2点にまとめることができるだろう。
(1) GPU 上の動き補償予測モジュールと符号化モジュールを、ギャップなく動作させ続けること。インテル® GPA プラットフォーム・アナライザー上で、GPU:ENCODE の2つのトラックを中心的にチェックし、アイドル状態になってしまってないか確認する。(2) 動き補償予測モジュールには、符号化モジュールと並列実行が可能な部分があるため、可能な限りこれが並列に実行されるように構成し、さらなるスループットの向上を図る。
GPU トラック上のギャップの確認
ここでは、構成1のアプリケーションをさらに解析しながら、より具体的に検討してみよう。
図 12 フレーム間のギャップ
図12のように、構成1の同期実行の状態を確認すると、フレーム間に無駄なギャップがあることが分かるだろう。ここが最適化されるべきポイントであることには疑いはない。ただ、着目すべきはアプリケーション・トラックにある 4.8ms のギャップなのではなく、より厳密には GPU:ENCODE トラックにあるギャップであることに注意しよう。このアイドル状態が GPU の利用率を押し下げる原因で、図12上では約 30-40% のギャップが確認できる。これは第1回でリアルタイム・モニター上で計測した 62% という数字とほぼ合致すると言ってよい。したがって、前述のエンコーダー・アプリケーションの最適化のポイントをあてはめれば、
(1) 図12上の 7.5ms の GPU のアイドル状態を作らないようにアプリケーションを構成し直す(2) さらにそれだけでなく、後続の ENCODE Stage1 と Stage2 は先行する符号化モジュールと並列動作可能であるため、その並列動作も可能とするようにも工夫すべき
ということになる。つまり短縮されるべき実行時間は、図12中の 7.5ms 分のギャップだけではなく、できれば 8.6ms 分を短縮することが望ましい。
これを実際のアプリケーションの開発の立場から見ると、次のフレームの EncodeFrameAsyc API をより早期に実行し、Encode_Submit、すなわちエンコードに必要となる入力フレームの GPU への転送とエンコードコマンドの発行をより早期に完了するようにする、ということである。
EncodeFrameAsyc APIを実行すべき具体的タイミング
では、「早期」とは具体的にどのタイミングを指すのであろうか。アイドル状態を作らない、という要件を充たすためには、符号化モジュールが「エンコードを終了する前」までに、次のフレームの Encode_Submit を完了すればよい(図13中の1)、ということになるが、これでは動き補償予測モジュールと符号化モジュールが並列動作できないので、要件(2)を充たさず十分とはいえない。したがって、よりパフォーマンスを引き出すためには、「先行する動き補償予測モジュールが終了する前」に、次のフレームの Encode_Submit が完了することが望ましいと言える(図13中の2)。
図 13 Encode_Submitが実行されるタイミング
では、そのように最適化された構成2のアプリケーションをインテル® GPA プラットフォーム・アナライザーで確認してみよう(図14)。
図 14 最適化されたアプリケーションのGPUトラック
図14を確認すると、動き補償予測モジュールと符号化モジュールがギャップなく、ほぼ 100% に近い利用率で活用され、かつ、一部のモジュールがオーバーラップして、並列実行されていることがわかるだろう。つまり、ハードウェア・アクセラレーションを効率的に利用すると同時に、もう一歩踏み込んで、異なるモジュールの並列性をも活用しているわけである。このように、実際のアプリケーションをインテル® GPA プラットフォーム・アナライザーで解析する際には、まずGPU:ENCODE の2つのトラックに着目し、それが十分に活用されているかを確認するのが近道であろう。
ハードウェア・アクセラレーションを利用したエンコード最適化のポイント
先行する動き補償予測モジュールが終了する前に、次のフレームの Encode_Submit を完了できるタイミングで EncodeFrameSync API を実行する。 |
GPUのハードウェア・アクセラレーションの利用率とパフォーマンス
以上が、GPUのハードウェア・アクセラレーションを利用する上でのエンコード・パフォーマンス最適化のポイントであるが、実は1点つじつまが合わない部分がある。気がついた読者の方はいるだろうか。図12の構成1のアプリケーションに戻ると、仮に最適化のポイントの要件を充たしたとして、得られる最高のスループットは、フレームあたり約 16-7ms であるはずである。したがって、計算上は約 60FPS 前後が最高のパフォーマンスだということになるはずである。にもかかわらず構成2のアプリケーションを計測した際には 108 FPS に到達していた。誤差を考慮しても 100 FPS オーバーの 1.5 倍以上のパフォーマンスというのは説明がつかない。そこで、構成1と構成2のズームレベルを同一にした上で両者を比較してみよう(インテル® GPA プラットフォーム・アナライザー上では左下で現在のZoom Levelが確認できる)。
図15 構成1と構成2のアプリケーションを比較したところ
両者を比較すると、符号化モジュールのそれぞれ実行時間はほぼ同様と考えてよいだろう。しかし、よく見ると個々のフレームの動き補償予測モジュールの実行時間が違う。構成2の方が、明らかに高速に処理が終了しているように見える。そこで、動き補償予測トラックの4つのステージの実行にかかっている時間をそれぞれ確認してみると、構成1が 12-14ms 程度であるのに対し、構成2のアプリケーションは 7-8ms 程度で終了している。倍速とはいかないまでも、1.8 倍程度は高速だ。この差はインテル® GPA プラットフォーム・アナライザー上からの解析ではまったく説明がつかない。そこで、この点を説明しておこう。
近時のインテル® Core™ プロセッサーにはターボ・ブーストの機能が搭載されていることはご存知の読者も多いだろう。そして、このターボ・ブースト機能は、Sandy Bridge 上では GPU 側にも搭載されている。ただ、動作は CPU 側とは異なる実装になっており、実は GPU の利用率が 90% を超えた時点でターボ・ブーストされる設定になっている(BIOSなどで OFF にしていなければ)。周波数設定は 2 段階で、本稿で用いたインテル® Core™ i5 2540Mプロセッサーの場合、ベースの周波数が 650MHz、ターボ・ブースト時の周波数は 1.3GHz である。
すでにおわかりのように構成1のアプリケーションはピークの利用率でも 62% 程度しかなく、したがって、ターボ・ブースとされることなく、動き補償予測トラックは 650MHz の周波数で動作している。一方構成2のアプリケーションは、100% に近い利用率の結果(計測上は 98%)、ターボ・ブーストされ、1.3GHz で動作しているのである。周波数が2倍になってもソフトウエアのパフォーマンスが当然 2 倍になるわけではないが、約 1.8 倍のパフォーマンスは、GPU のターボ・ブースト機能によるものである。この GPU のターボが有効になっているかどうかを明確に確認できるツールはないが、インテル® GPA プラットフォーム・アナライザーである程度は確認可能である。構成2のアプリケーションで、エンコード開始直後10フレーム前後を境に、動き補償予測モジュールの速度が、12-13ms から 8ms 程度に変化することが確認することが可能である。逆にいえば、エンコードを開始してからターボが有効に効果を発揮するまでには一定のタイムラグがあるということもわかる。
非同期フレームプールとハードウェアクセラレーション
最後にもう一度、エンコーダー・アプリケーションでハードウェア・アクセラレーションを利用してパフォーマンスを引き出すポイントをまとめておこう。
ポイント1:先行する動き補償予測モジュールが終了する前に、次のフレームの Encode_Submit が完了できるタイミングで EncodeFrameAsync API を実行する。
ポイント2:それが難しい場合でも遅くとも符号化モジュールの完了前には、Encode_Submit が完了するようにして、ハードウェア・アクセラレーション・モジュールの 100% に近い利用率を保つ。
ポイント3:アプリケーションの性質上、ポイント2の要件を充たすことさえ難しい場合でも、何とか90% の利用率を維持して、GPU のターボ機能の恩恵を受けられるように努力する。この恩恵を受けられるだけで、1.5 倍以上のパフォーマンスが出せるのだから、是非とも意識しておきたいところである。
上記の要件を充たすためには、アプリケーション側でフレームプールを利用して、当該フレームの同期以前に次のフレームの EncodeFrameAsync API を実行することが重要となる。問題は、何フレーム分プールすればいいのか、であるが、これに対しては、上記ポイント1の要件を常に充たすようなタイミングが実現できる枚数、というのが答えにならざるをえない。ポイント1はすなわち、動き補償予測モジュールが終了する前の Encode_Submit の完了ということであるから、プールに必要な枚数は、GPU アクセラレーション・モジュールの実行時間に依存する。これが 20ms もかかるのであれば2枚程度でもよいかもしれないが、10ms 以内なら 3 枚は欲しいかもしれない。
これを言い換えると、理想的なプール数は、プロセッサーの採用する具体的なアクセラレーション・アーキテクチャーに依存して変わる可能性があるということである。
MSDK が mfxFrameAllocRequest::NumFrameSuggested という API をもっているのはその趣旨で、この API は対象になっているプラットフォームごとに最適なプール数を返してくれる。例えば、次世代のプロセッサーで、動き補償予測モジュールが 2 倍程度高速化された場合、上記のポイント1を充たすために Sandy Bridge より多くのフレーム数をプールしなければならなくなる可能性もあるだろう。したがって、ここでは、上記 API を活用することをお勧めさせて頂きたい。
著者紹介
岩本 成文 氏Software Engineer, Software and Services Group, Intel Corporation