以下问题与设计更相关,而不是实际编码。我不知道是否有专门针对此类问题的术语,因此我将继续举例。
我有一些根本没有优化的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语句的一组情况下采用值。
答案 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语句。
我现在受到启发去研究这个临界值。 :-)