5.5.5.7. パイプ呼び出し順序の強制
インテル® FPGA SDK for OpenCL™オフライン・コンパイラーが計算ユニットを生成する際、それぞれが独立している命令のすべてに対し、命令レベルの並列性を構築するわけではありません。そのため、パイプの読み出しおよび書き込み動作は、それらに制御やデータの依存性がない場合でも、互いに独立して実行されない可能性があります。パイプ呼び出しが相互に通信する場合や、パイプが外部デバイスにデータを書き込む場合に、デッドロックが発生する可能性があります。
次のコード例は、producerカーネルとconsumerカーネルで構成されています。パイプc0とc1はバッファーされていないパイプです。c0とc1からのパイプ読み出し動作のスケジュールは、c0とc1へのパイプ書き込み動作と逆の順序になる可能性があります。つまり、producerカーネルが最初にc0に書き込む一方で、consumerカーネルはc1から読み出す可能性があるということです。consumerカーネルが空のパイプから読み出しているため、このパイプ呼び出しにおけるスケジューリングの変更は、デッドロックを発生させる可能性があります。
__kernel void producer (__global const uint * restrict src,
const uint iterations,
write_only pipe uint __attribute__((blocking)) c0,
write_only pipe uint __attribute__((blocking)) c1)
{
for (int i = 0; i < iterations; i++) {
write_pipe (c0, &src[2*i ]);
write_pipe (c1, &src[2*i+1]);
}
}
__kernel void consumer (__global uint * restrict dst,
const uint iterations,
read_only pipe uint __attribute__((blocking)) c0,
read_only pipe uint __attribute__((blocking)) c1)
{
for (int i = 0; i < iterations; i++) {
read_pipe (c0, &dst[2*i+1]);
read_pipe( c1, &dst[2*i]);
}
}
__kernel void producer (__global const uint * src,
const uint iterations,
write_only_pipe uint __attribute__((blocking)) c0,
write_only_pipe uint __attribute__((blocking)) c1)
{
for (int i = 0; i < iterations; i++)
{
write_pipe(c0, &src[2*i ]);
mem_fence(CLK_CHANNEL_MEM_FENCE);
write_pipe(c1, &src[2*i+1]);
}
}
__kernel void consumer (__global uint * dst;
const uint iterations,
read_only_pipe uint __attribute__((blocking)) c0,
read_only_pipe uint __attribute__((blocking)) c1)
{
for(int i = 0; i < iterations; i++)
{
read_pipe(c0, &dst[2*i ]);
mem_fence(CLK_CHANNEL_MEM_FENCE);
read_pipe(c1, &dst[2*i+1]);
}
}
この例では、producerカーネルのmem_fenceは、c0へのパイプ書き込み動作がc1より先に発生するようにしています。同様にconsumerカーネルのmem_fenceは、c0からの読み取り動作がc1より先に行われるようにしています
パイプ使用時におけるカーネル間のメモリーの一貫性の定義
__kernel void producer (__global const uint * restrict src,
const uint iterations,
write_only pipe uint __attribute__((blocking)) c0,
write_only pipe uint __attribute__((blocking)) c1)
{
for (int i = 0; i < iterations; i++)
{
write_pipe(c0, &src[2*i]);
mem_fence(CLK_CHANNEL_MEM_FENCE | CLK_GLOBAL_MEM_FENCE);
write_pipe(c1, &src[2*i+1]);
}
}
このカーネルでmem_fence関数は、c0への書き込み動作とsrc[2*i] へのメモリーアクセスが、c1への書き込み動作とsrc[2*i+1] へのメモリーアクセスの前に必ず実行されるようにしています。これにより、c0に書き込まれたデータは、c1にデータが書き込まれる前に読み出しパイプから見えるようになります。