OpenCL内核中的变量';对于循环';降低性能

OpenCL内核中的变量';对于循环';降低性能,opencl,Opencl,我的内核中有一个for循环,我对它进行了硬编码,以迭代代码中固定数量的循环: for (int kk = 0; kk < 50000; kk++) { <... my code here ...> } 这与之前硬编码的值相同,内核的性能几乎减半 我正试图找出导致性能下降的原因。我想这与OpenCL编译器不能有效地展开循环有关 有没有一种方法可以做到我想做的事情而不会导致性能损失 更新:以下是玩“#pragma unroll”的一些结果 不幸的是,展开循环似乎不能解决我的性

我的内核中有一个for循环,我对它进行了硬编码,以迭代代码中固定数量的循环:

for (int kk = 0; kk < 50000; kk++)
{
  <... my code here ...>
}
这与之前硬编码的值相同,内核的性能几乎减半

我正试图找出导致性能下降的原因。我想这与OpenCL编译器不能有效地展开循环有关

有没有一种方法可以做到我想做的事情而不会导致性能损失

更新:以下是玩“#pragma unroll”的一些结果

不幸的是,展开循环似乎不能解决我的性能问题

即使展开硬编码循环也会降低性能

以下是硬编码值的正常循环(最佳性能):

for(int-kk=0;kk<50000;kk++)
//执行时间=0.18(40180英里/秒)
如果我展开循环,事情会变得更糟:

#pragma unroll
// or #pragma unroll 50000
for (int kk = 0; kk < 50000; kk++)
// Time to execute = 0.22 (33000 Mi ops/sec)
#pragma展开
//或#pragma展开50000
对于(int kk=0;kk<50000;kk++)
//执行时间=0.22(33000英里/秒)
下面是使用变量num_loops=50000的循环:

for (int kk = 0; kk < num_loops; kk++)
// Time to execute = 0.26 (27760 Mi ops/sec)

#pragma unroll 50000
for (int kk = 0; kk < num_loops; kk++)
// Time to execute = 0.26 (27760 Mi ops/sec)

#pragma unroll
for (int kk = 0; kk < num_loops; kk++)
// Time to execute = 0.24 (30280 Mi ops/sec)
for(int-kk=0;kk
如果将num#u loops变量与直接的“#pragma unroll”一起使用,情况确实会有所改善,但是即使这样,性能也比硬编码的展开版本慢25%左右


关于如何使用num_循环作为循环变量而不影响性能,还有其他想法吗

是的,性能下降的最可能原因是编译器无法展开循环。你可以尝试做一些事情来改善这种情况


您可以将参数定义为通过程序构建选项传递的预处理器宏。这是一个常见的技巧,用于将只有在运行时才知道的值作为编译时常量构建到内核中。例如:

clBuildProgram(program, 1, &device, "-Dnum_loops=50000", NULL, NULL);
您可以使用
sprintf
动态构造构建选项,使其更灵活。显然,只有在不需要经常更改参数的情况下,这才是值得的,这样重新编译的开销就不会成为问题


您可以调查您的OpenCL平台是否使用任何可以向编译器提示循环展开的杂注。例如,一些OpenCL编译器识别
#pragma unroll
(或类似)。OpenCL2.0对此有一个属性:
\uuuuuuuuuuu属性((OpenCL\uUnroll\u提示))


您可以手动展开循环。这看起来如何取决于您可以对
num\u loops
参数做出什么样的假设。例如,如果您知道(或可以确保)它始终是4的倍数,则可以执行以下操作:

for (int kk = 0; kk < num_loops;)
{
  <... more code here ...>
  kk++;
  <... more code here ...>
  kk++;
  <... more code here ...>
  kk++;
  <... more code here ...>
  kk++;
}
for(int kk=0;kk

即使您不能做出这样的假设,您仍然应该能够执行手动展开,但它可能需要一些额外的工作(例如,完成任何剩余的迭代)。

for循环反复计算(;;)中的第二条语句,以确定是否继续循环。这样的条件语句总是导致控制流分叉并丢弃不需要的计算,这是浪费


正确的方法是向内核中添加另一个维度,并将该维度完全放在一个工作组中,以便在一个计算单元中按顺序执行

您是否尝试指定更合理的展开因子,如
#pragma unroll 2
#pragma unroll 4
?您是否尝试过以较小的因子(2、4等)手动展开循环体?你的目标是哪个OpenCL平台/设备?我是一个OpenCL新手,所以请容忍我。为什么你说一个很小的展开因子更合理?较小的因素没有帮助。我确实手动展开了一个小数字10,这使性能恢复,因此接近使用硬编码的num_loops值。我正在使用Nvidia Tesla M2090及其v4.2 SDK。展开数千次循环迭代可能会导致性能下降,因为您将显著增加生成代码的大小,可能会影响指令缓存。展开几次迭代是一个很好的折衷方案,通过这种折衷方案,您可以使生成的代码相当紧凑,但仍然可以获得编译器可以使用的较少分支和更多指令级并行性的好处。要展开的迭代次数也取决于循环体的复杂程度。直到今天早上,我才尝试手动展开循环。通过手动展开10个循环,我的性能接近硬编码num_loops变量的性能。谢谢你的推荐。我尝试了许多不同的“#pragma unroll”值,这些值甚至没有让我接近我的位置。我仍然有点困惑,很难说当你使用
#pragma unroll
时编译器会做什么。这实际上只是给编译器一个提示,提示您希望展开循环,但它可能决定无法展开循环,或者以一种效率不高的方式展开循环。如果您感兴趣,可以使用
clGetProgramInfo(…,CL\u PROGRAM\u BINARIES,…)
获取编译器生成的PTX,并检查它以查看编译器实际执行的展开类型(如果有)。
for (int kk = 0; kk < num_loops; kk++)
// Time to execute = 0.26 (27760 Mi ops/sec)

#pragma unroll 50000
for (int kk = 0; kk < num_loops; kk++)
// Time to execute = 0.26 (27760 Mi ops/sec)

#pragma unroll
for (int kk = 0; kk < num_loops; kk++)
// Time to execute = 0.24 (30280 Mi ops/sec)
clBuildProgram(program, 1, &device, "-Dnum_loops=50000", NULL, NULL);
for (int kk = 0; kk < num_loops;)
{
  <... more code here ...>
  kk++;
  <... more code here ...>
  kk++;
  <... more code here ...>
  kk++;
  <... more code here ...>
  kk++;
}