タスクの並列処理を使用すると、データフロー並列処理の利点を活かすことができます。ループの並列処理とは異なり、タスクの並列処理ではタスク間で発生するバッファリングの利点を活かして、全実行ユニット (タスク) を並列実行できます。
次に例を示します。
void run (ap_uint<16> in[1024],
ap_uint<16> out[1024]
) {
ap_uint<16> tmp[128];
for(int i = 0; i<8; i++) {
processA(&(in[i*128]), tmp);
processB(tmp, &(out[i*128]));
}
}
このコードを実行すると、processA
および processB
関数が順に 128 回実行されます。ループ内の processA
および processB
のレイテンシが合わせて 278 だとすると、総レイテンシは次のように見積もられます。
余分なサイクルはループ設定が原因で、これはスケジュール ビューアーで確認できます。
C/C++ コードの場合、タスク並列処理は for ループに DATAFLOW プラグマを追加すると実行されます。
#pragma HLS DATAFLOW
OpenCL API コードでは、for ループ前に次の属性を追加します。
__attribute__ ((xcl_dataflow))
このトピックの詳細は、データフロー最適化、HLS プラグマ、および OpenCL 属性 を参照してください。
HLS レポートの見積もりで示したように、タスク間にダブル (ピンポン) バッファーを使用すると、全体的なパフォーマンスを大幅に改善できます。
この場合、異なる反復で異なるタスクが並行実行されるので、デザインの全体的なレイテンシがほぼ 1/2 になります。各関数の処理に 139 サイクルかかり、128 反復が完全にオーバーラップしているので、総レイテンシは次のようになります。
(1x only processA + 127x both processes + 1x only processB) * 139 cycles = 17931 cycles
タスクの並列処理は、インプリメンテーションでパフォーマンスを改善できる効果的な手法ですが、DATAFLOW プラグマを特定の任意のコード部分に適用するのがどれくらい効率的かは、状況によってかなり異なることがあります。DATAFLOW プラグマの最終的なインプリメンテーションを理解するには、個々のタスクの実行パターンを確認することが必要です。Vitis コア開発キットには、同時実行を示す Detailed Kernel Trace が提供されています。
前の図に示すように、この Detailed Kernel Trace の場合、データフロー フロー ループの始点が表示されます。processA
はループの最初に即座に開始し、processB
は processA
の終了を待ってから最初の反復を開始します。processB
がループの最初の反復を完了している間に、processA
は 2 回目の反復の演算を開始します。
この情報のより抽象的な表示は、ホストとデバイスのアクティビティの アプリケーション タイムライン に示されます。