在我的内核中,有必要对一个小的查找表进行大量随机访问(只有8个32位整数)。每个内核都有一个唯一的查找表。下面是内核的简化版本,用于说明如何使用查找表。
__kernel void some_kernel(
__global uint* global_table,
__global uint* X,
__global uint* Y) {
size_t gsi = get_global_size(0);
size_t gid = get_global_id(0);
__private uint LUT[8]; // 8 words of of global_table is copied to LUT
// Y is assigned a value from the lookup table based on the current value of X
for (size_t i = 0; i < n; i++) {
Y[i*gsi+gid] = LUT[X[i*gsi+gid]];
}
}
由于体积小,我通过将表保存在__private内存空间中获得最佳性能。但是,由于访问查找表的随机性,仍然存在很大的性能损失。删除查找表代码(例如,用简单的算术运算代替),虽然内核会提供错误的答案,但性能提高了3倍。
有更好的方法吗?我是否忽略了一些OpenCL功能,它为非常小的内存块提供了有效的随机访问?使用矢量类型可以有一个有效的解决方案吗?
[编辑]注意,X的最大值是7,但Y的最大值是2 ^ 32-1。换句话说,正在使用查找表的所有位,因此无法将其打包成较小的表示。
答案 0 :(得分:4)
我能想到的最快的解决方案是首先不使用数组:改为使用单个变量并使用某种访问函数来访问它们,就好像它们是一个数组一样。 IIRC(至少对于AMD编译器,但我很确定NVidia也是如此):通常,数组总是存储在内存中,而标量可以存储在寄存器中。 (但我的想法有点模糊 - 我可能错了!)
即使你需要一个巨大的开关声明:
uint4 arr0123, arr4567;
uint getLUT(int x) {
switch (x) {
case 0: return arr0123.r0;
case 1: return arr0123.r1;
case 2: return arr0123.r2;
case 3: return arr0123.r3;
case 4: return arr4567.r0;
case 5: return arr4567.r1;
case 6: return arr4567.r2;
case 7: default: return arr4567.r3;
}
}
...与__private数组相比,你可能仍然领先于性能,因为假设arr变量全部适合寄存器,纯粹是ALU绑定的。 (当然,假设你有足够的备用寄存器用于arr变量。)
注意,有些OpenCL目标甚至没有拥有私有内存,而你在那里声明的任何东西都只是去了__global。使用寄存器存储是一个更大的胜利。
当然,这种LUT方法初始化的速度可能较慢,因为您需要至少两次单独的内存读取才能从全局内存中复制LUT数据。
答案 1 :(得分:1)
正如rtollert所说,由实现决定LUT []是放在寄存器中还是放在全局存储器中。通常情况下,内核中的数组是禁忌,但由于它很小,因此很难说它将被放置在何处。假设将LUT []置于寄存器中,我会说它与一个简单的算术运算相比花费很长时间的原因并不是因为它是随机访问的,而是因为每个工作项增加了8个(编辑:显然更多)全局读取X以计算LUT指数。根据省略的内容,你可以做一些像Y [i * gsi + gid] = global_table [someIndex + X [i * gsi + gid]]];?