OpenCL,用算术运算代替分支

时间:2018-07-09 12:45:15

标签: parallel-processing opencl gpu

以下问题与设计更相关,而不是实际编码。我不知道是否有专门针对此类问题的术语,因此我将继续举例。

我有一些根本没有优化的openCL代码,并且在内核中本质上是类似于以下内容的switch语句

switch(const) {
   case const_a : do_something_a(...); break;
   case const_b : do_something_b(....); break;
   ... //etc
}

由于时间太长,我无法写出实际的陈述。作为一个简单的示例,请考虑以下switch语句:

int a;
switch(input):
  case 13 : {a = 3; break;}
  case 1 : {a = 7; break;}
  case 23 : {a = 1; break;}
  default : {...}

问题是...用这样的表达式更改这样的开关会更好吗

a = (input == 13)*3 + (input == 1)*7 + (input == 23)

如果不是,是否有可能提高效率?

您可以假设input仅在switch语句的一组情况下采用值。

1 个答案:

答案 0 :(得分:0)

您发现了一个有趣的问题,GPU编译器在努力解决。一般建议是尽量不要分支。要使之成为可能的技巧是将内核(如上所述)和预处理器(程序时定义)分开。以此公理为基础进行GPU算法开发研究。

由于固有的差异(在SIMD线程/经线中通道=工作项),到处分支都不会获得很大的效率。请记住,所有这些通道必须一起执行。因此,在所有人都走不同路径的交换机中,其他所有人都无声地等待着他们的“案件”执行。现在,如果input始终是相同的值,那么它仍然可以是一个胜利。

另一个流行的选择是表间接访问。

kernel void foo(const int *t, ...)
  ...
  a = tbl[input];

根据硬件,输入和问题大小,这种情况下也会遇到一些问题。

在没有更具体的上下文的情况下,我可以想到其中任何一个都可以运行良好或运行不佳的情况。

  • 切换(或大型if-then-else链)。

    • PROS:如果所有工作项通常都采用相同的路径(input大部分是相同的值),那么它将很有效率。您还可以编写一条if-then-else链,将最常见的情况放在第一位。 (在GPU上,切换不一定像间接跳转那样容易,因为存在多个工作项,并且它们可能采用不同的路径。)

    • 缺点:可能生成大量程序代码,并且可能耗尽指令缓存。根据需要评估的案例数量,遍布各地分支机构的成本可能会有所提高。最好使用谓词代码遍历计算。

  • 谓词代码(您的(input == 13)*3 ...代码)。

    • PROS:这可能会生成较小的程序,并降低I $。 (查看OpenCL select函数以查看适合您情况的更通用方法。)

    • 缺点:我们基本上已经做出决定,并决定评估每个“开关中的情况”。如果input通常是相同的值,那么我们在这里浪费时间。

  • 基于查找表的方法(我的示例)。

    • PROS:如果您要评估的开关有大量用例(分支),但是可以用整数索引,那么您可能会优先使用查找表。在某些硬件上,这意味着从全局内存中读取(很远)。其他体系结构具有专用的常量缓存,但是我知道向量查找将序列化(每个通道K个周期)。因此,它可能仅比全局内存表好一点。但是,生成的代码表查询将很短(I $友好),并且随着分支(case语句)的数量增加,这将在限制内获胜。这种方法还可以很好地处理input值的均匀/分散分布。

    • CONS:与分支相比,从全局内存中进行读取(或从常量高速缓存进行序列化访问)具有较大的延迟。在某些情况下,为了消除额外的内存流量,我已经看到编译器将查找表转换为if-then-else / switch链。我们很少有100个元素的case语句。

我现在受到启发去研究这个临界值。 :-)