この記事は、インテル® デベロッパー・ゾーンに公開されている「Heterogeneous Computing Pipelining」(https://software.intel.com/en-us/articles/heterogeneous-computing-pipelining/) の日本語参考訳です。
目次
- はじめに
- インテル® Core™ プロセッサー・ファミリーおよびインテル® Atom™ プロセッサー・ファミリーとヘテロジニアス処理アーキテクチャー
- 概要
- モデル
- メモリー要件
- 実装 – インテル® TBB+OpenMP*+OpenCL*
- パフォーマンス結果
- まとめ
- 著者紹介
- 参考文献
1. はじめに
大部分のイメージやビデオ効果は、2 つ以上のステージから成ります。ソフトウェア開発者は、それらをしばしばシーケンシャルに 1 ステージずつ処理します。このようなアルゴリズムのパフォーマンスをチューニングする簡単な方法は、ベクトル化、マルチスレッド化そして (もしくは) グラフィックスへのオフロードを利用して各ステージを個別に最適化することです。しかし、ステージの処理はシーケンシャルに行われます。インテル® プロセッサーは、最適な電力消費で最高のパフォーマンスを達成するため、アルゴリズムを並列に処理する複数の機能とオフロード機能を提供します。開発者は、チップ上の他の処理コンポーネントを利用せず劇的なパフォーマンスの向上を無視して、常に 1 つのデバイス向けに (例えば、CPU もしくはインテル® プロセッサー・グラフィックス) アルゴリズムを最適化することができます。アプリケーションが異なるデバイス間でワークロードを分散してそれらを並列に処理する場合、より複雑になります。この記事では、CPU のソフトウェア・パイプラインと OpenCL* コードによって、インテル CPU 上で利用可能なすべてのリソースを使用する効率良い方法を紹介します。
2. インテル® Core™ プロセッサー・ファミリーおよびインテル® Atom™ プロセッサー・ファミリーとヘテロジニアス処理アーキテクチャー
インテル® Core™ プロセッサー・ファミリーとインテル® Atom™ プロセッサー・ファミリーは、ヘテロジニアス計算とメディア処理向けの複数のリソースを使ったシステム・オン・チップ (SoC) ソリューションを提供します。図 1 にインテル® Core™ M プロセッサーのシリコンダイのレイアウトを示します。
図 1. インテル® Core™ M プロセッサーのシリコンダイのレイアウト。この SoC は、オレンジ色の
破線で囲まれた 2 つの CPU コアを持ちます。青色の破線内は、インテル® HD グラフィックス
5300 です。これは、インテル® プロセッサー・グラフィックスの 1 つの具体化といえます [7]。
インテル® マイクロプロセッサーは、単一の共有シリコンダイ上に複数の CPU コア、インテル® プロセッサー・グラフィックス、そしてそのほかの機能を統合した複雑な SoC です。アーキテクチャーは、CPU ごとのコアクロック、プロセッサー・グラフィックスのクロック、そしてリング相互接続のクロックを含むユニークなクロックドメインを実装しています。SoC アーキテクチャーは、拡張性を考慮して設計され、コンポーネント間の接続は効率よく行われています。
図 2. インテル® Core™ M プロセッサー SoC とリング相互接続アーキテクチャー [7]。
第 2 世代インテル® Core™ プロセッサーでは、インテル® Media SDK を介してインテル® クイック・シンク・ビデオ (インテル® QSV) で導入されるヘテロジニアス・メディア処理が拡張されました [9]。第 3 世代インテル® Core™ プロセッサーでは、3 つの技術革新と OpenCL* 1.2 がサポートされました。これにより、アプリケーションはインテル® プロセッサー・グラフィックス上でヘテロジニアス計算を行うことができます [7]。新しいインテル® Core™ M プロセッサーは、OpenCL* 2.0 をサポートし、より柔軟性の高いプログラミングを可能にしています。インテル® Core™ プロセッサーでは、すでに共有物理メモリーがサポートされ、アプリケーションが CPU とインテル® プロセッサー・グラフィックス間でデータを共有することができますが、OpenCL* 2.0 の共有仮想メモリーのサポートは、アプリケーションが 2 つのデバイス間でシームレスにデータ構造を共有することを可能にします。
3. 概要
インテル® プロセッサー・アーキテクチャーは、CPU とプロセッサー・グラフィックス間でリソース (キャッシュなど) を供することで、アプリケーションに対し結合力のある環境を提供します。私たちは、ソフトウェア・パイプラインを介してこれらの機能の恩恵を受けることができます。
では、N ステージからなる一般的なアルゴリズムの実行モデルを確認してみましょう。
図 3. 一般的なアルゴリズムの実行モデル
Ti は、i 番目のステージを実行する時間で、総実行時間は ∑ Ti であると推測されます。
簡単な最適化の方法は、スカラー・コード・チューニング、マルチスレッド化、ベクトル化 [1]、もしくはプロセッサー・グラフィックスへのオフロード (OpenCL* など) の手法により、個別のステージのパフォーマンスをチューニングすることです。これはあくまで最初のステップであり、パフォーマンスの可能性を損ねることがあります。例えば、すべてのステージにほぼ同じ時間がかかる場合、5 つのステージのうち 1 つを 2 倍に高速化しても、全体のパフォーマンス向上は 10% 以下です。
Eric Reed、Nicholas Chen および Ralph Johnson による「Expressing Pipeline Parallelism using TBB constructs (インテル® TBB 構文によるパイプライン並列の表現)」で説明されるように、複数の処理 (ここではフレーム) を同時に実行することで、並列化を実現することができます [2]。しかし、それは同時に複数の入力データ (フレーム) がある場合にのみ適用できます。そのため、フォト編集の例では利用できないでしょう。また、この手法は利用可能なリソースのアンダーサブスクライブ状態を招き、さたにメモリー帯域幅の増加につながります。この手法は、多くのビデオ処理アルゴリズムにおいて有効であるとして、その価値が評価されています。
ここでは、パフォーマンスと局所性を改善し、利用可能なすべてのリソースを効率よく利用することができる、両方のデバイス上で並列ステージを実行する方法を示します。両者は、同時に利用することができます。
4. モデル
分かりやすく説明するため、ここでは 2 つのステージのみの短縮版エフェクトについて述べます。
図 4. 2 ステージのアルゴリズム
実行を CPU と GPU 間で分散したいため、最適なそして最適化されたステージ 1 とステージ 2 のアルゴリズムを実装する必要があります。ステージ 1 は CPU に、そしてステージ 2 は GPU 最適化されていると仮定します。図 5 に示すように、正しいパイプライン・モデルを実現するため、 1 つはアルゴリズムに効率良いタイル処理を実装する必要があります。
図 5. タイル化されたアルゴリズム
この手法の鍵は、図 6 に示すように、前のタイルの処理を GPU 上で終える間に、CPU 上で次のタイルのステージ 1 を実行することです。中間バッファーへストアされるステージ 1 の出力データは、次の反復で GPU 上のステージ 2 のアルゴリズムの出力を生成するために使用されます。CPU と GPU の両方はお互いのタスクの完了を待ち、処理過程を繰り返します。2 つのパイプラインが同時に実行されるため、ステージ間でデータを引き渡す 2 つの中間バッファー (一度割り当てが行われ適切に循環される) が必要になります。
図 6. パイプライン実行
t1 と t2 はそれぞれステージ 1 とステージ 2 の実行時間であり、総実行時間は T = t1 + t2 となります。スレッド化と同期のオーバーヘッドを無視すると、ウォール時間は T=max(t1,t2)+(t1 + t2)/N となります。N はタイル数です。limN→∞T = max(t1,t2) を理解することで、理論的には実行するエフェクトの一方を隠匿し、ウォール時間を次の係数で減少できます。
残念ながら、同期とデータ転送のペナルティーはタイル数に比例して増加するため、常に “完璧な” 並列実行を行うことはできません。
5. メモリー要件
冒頭で述べたように、提案されたアルゴリズムがメモリー要件を減らすだけでなく、データの局所性を改善する方法を示します。S は入力データ (例えば、イメージバッファー) を表し、S の中間データをセーブするためスペースと同じ量の出力データのスペースが必要であり、ここでは総ワーキングセットのサイズは 3S であると仮定します。提案されたモデルでは、S/N サイズの 2 つの中間バッファーのみを必要とします。そして、総メモリー要件は、M=(2+2⁄N)S となります (すべての N > 2 向けの < 3S)。したがって、総メモリー要件は次のように期待できます。
次のことに注意、。
ステージ 2 が同様の入力データを使用するケースでは、インフライトのタイルバッファーの数は、time – 2 * Input + 2 * Itermidiate + 1 * Output となります。その後、ワーキングセットのサイズは、m=5S/N 対 N = 4 で 1.6 倍以下で、N = 16 では 6 倍少ないベースラインの実装で 2S となります。
一方、前述したように、データの局所性を向上し、ワーキングセットのサイズを減らすことでもパフォーマンスを改善できます。データをデバイス間で転送するレイテンシーを回避するため、m をラストレベル・キャッシュ (LLC) サイズと同じか小さくなるようにタイル数を選択した場合、興味深い一例といえます。
6. 実装 – インテル® TBB+OpenMP*+OpenCL*
私たちは、この手法を用いて 2 ステージのフォト編集エフェクトを実装しました。スカラー最適化からベクトル化、そして OpenMP* と OpenCL* を使用した並列計算まですべての最適化を導入しました。
図 5 に示すように、私たちは始めに “タイル化した” バージョンをステージ 1 (CPU/インテル® AVX2) とステージ 2 (OpenCL*) のアルゴリズムに実装し、ステージ 1 のパフォーマンス改善とすべての CPU コアを活用するため、OpenMP* を利用しました。
ソフトウェア・パイプラインと並列実行を適用するため、インテル® TBB [5] を使用しました。インテル® TBB は、オープンソース版、もしくはインテル® C/C++ コンパイラーに同梱される商用版が利用できます。ソフトウェア・パイプラインには、tbb::pipeline を使用し、ステージクラス (tbb::filter) とトークン構造を定義しました。トークンは、ステージ間で転送され、タイル情報とそのほかの中間データを含んでいます。インテル® TBB は、(void *) オペレーターの入力パラメーターとして、次のステージへのトークン転送を管理します。
最高のパフォーマンスを達成するには、OpenCL* のゼロコピー機能 [6] を使用します。これは、 OpenCL* 2.0 のスタックでインテル® グラフィックス・アーキテクチャーもしくは共有仮想メモリー (SVM) で利用可能で、Broadwell (開発コード名) のインテル® グラフィックスからサポートされています。
struct token_t { void *buffer; // 中間バッファー int tile_id; // タイル数 }; class Stage1: public tbb::filter { public: void* operator() (void *) { if(tile_id == num_tiles) return null; // TBB にパイプライン実行の停止を通知 // トークンを作成し、事前に割り当てられたバッファーを選択 token_t *t=new token_t(); t->tile_id = tile_id++; t->buffer = buffers[tile_id % 2]; // バッファーの1つを選択 DoStage1_CPU(t, input); // CPUでタイルを処理 return t; // 次のステージへ中間データを転送 } }; class Stage2: public tbb::filter { public: void* operator(void* token) { token_t *t = (token_t*)token; // 以前のステージからデータを受け取る DoStage2_OpenCL(output, t); // プロセッサー Gfx 上で 2 番目の // ステージを処理 // そして出力データを書き込み delete t; // トークンを破棄 return 0; // パイプラインをフラッシュ } } // その後、pipeline(tbb::pipeline [4]) の作成と単純なロックを実行 // ステージの作成と初期化 Stage1 s1(input, W, H); Stage2 s2(input, output, W,H); // パイプラインを作成 tbb::pipeline ppln; // filters(stages) を追加 ppln.add_filter(s1); ppln.add_filter(s2); // 2 つのパイプラインを実行 // 1 つは CPU 上で実行される // その間、もう一方はプロセッサー Gfx で実行される // 次の”ステップ”では逆転 ppln.run(2);
7. パフォーマンス結果
無制限のタイル数を持つことが理想的ですが、マルチスレッド化と同期のオーバーヘッドがアルゴリズムの効率を損ねる可能性があります。
第 4 世代インテル® Core™ i7-4702HQ プロセッサー (2.2GHz) で多くのタイルを処理したところ、シリアル実装に比べ 1.7 倍全体のパフォーマンスが向上しました。このプラットフォーム上では、4 から 8 のタイルが私たちのアルゴリズムに最適であることが分かりました。
図 7. エフェクトの実行時間
8. まとめ
この記事では、利用可能な計算リソース間でワークロードを分散する効果的なアルゴリズムを示し、CPU とプロセッサー・グラフィックスで同時にアルゴリズムを実行することで、1.7 倍のパフォーマンス向上を達成できました。このアプローチは、インテル® プロセッサーで利用可能なデバイスや機能を活用するように拡張できます。インテル® プロセッサー・グラフィックスにおける最も一般的なハードウェア機能は、インテル® クイック・シンク・ビデオ (インテル® QSV) です [9]。アプリケーションは、処理ステージを分割する同じアプローチを、フレームをエンコードする超高速な固定関数ブロックの供給に利用できます。
9. 著者紹介
イリア・アルバートは、アプリケーション・エンジニアとして 5 年間インテルに勤続しており、アルゴリズムの最適化、CPU マイクロアーキテクチャーそして OpenCL* に精通しています。数学と情報セキュリティーの修士号を持っています。彼は、妻と共にアリゾナ州とカリフォルニア州の自然の驚異を探求することをこよなく愛します。 |
10. 参考文献
- “インテルのベクトル化ツール” https://www.isus.jp/article/intel-vectorization-tools/
- “Expressing Pipeline Parallelism using TBB constructs (TBB 構文を使用してパイプライン並列処理を表現する)”, Eric Reed, Nicholas Chen, Ralpf Johnson
https://www.ideals.illinois.edu/bitstream/handle/2142/25908/reedchenjohnson.pdf?sequence=2 - “インテル® アーキテクチャー向け OpenCL* ドライバーとランタイムのダウンロード”,
https://software.intel.com/en-us/articles/opencl-drivers - “pipeline Class (パイプライン・クラス)”,
http://www.threadingbuildingblocks.org/docs/help/reference/algorithms/pipeline_cls.htm - “インテル® スレッディング・ビルディング・ブロック (インテル® TBB)”,
https://www.threadingbuildingblocks.org/ - “インテル® プロセッサー・グラフィックスでバッファーコピーを最小限に抑えてパフォーマンスを向上する方法”, https://www.isus.jp/article/visual-computing/opencl-12-how-to-increase-performance-by-minimizing-buffer-copies/
- “インテル® プロセッサー・グラフィックス”, https://www.isus.jp/article/idz/vc/intel-graphics-developers-guides/.
- “インテル® SDK for OpenCL* Applications”, https://software.intel.com/en-us/intel-opencl/
- “インテル® Media SDK”, https://www.isus.jp/article/intel-software-dev-products/intel-media-sdk/