我在有和没有分支的过滤器内核上有一个奇怪的性能反转。具有分支的内核运行速度比没有分支的内核快1.5倍。
基本上我需要对一束辐射光线进行排序然后应用交互内核。由于有很多附带的数据,我不能多次使用thrust :: sort_by_key()这样的东西。
算法的想法:
我使用了这篇Nvidia帖子https://devblogs.nvidia.com/parallelforall/cuda-pro-tip-optimized-filtering-warp-aggregated-atomics/
中描述的技巧我的第一个内核包含一个内部循环分支,运行时间约为5毫秒:
int active;
int leader;
int warp_progress;
for (int i = 0; i != hit_interaction_count; ++i)
{
if (i == decision)
{
active = __ballot(1);
leader = __ffs(active) - 1;
warp_progress = __popc(active);
}
}
我的第二个内核使用两个元素的查找表,不使用分支并运行~8ms:
int active = 0;
for (int i = 0; i != hit_interaction_count; ++i)
{
const int masks[2] = { 0, ~0 };
int mask = masks[i == decision];
active |= (mask & __ballot(mask));
}
int leader = __ffs(active) - 1;
int warp_progress = __popc(active);
共同部分:
int warp_offset;
if (lane_id() == leader)
warp_offset = atomicAdd(&interactions_offsets[decision], warp_progress);
warp_offset = warp_broadcast(warp_offset, leader);
...copy data here...
怎么会这样?有没有办法实现这样的过滤器内核,所以它运行得比分支一个?
UPD:完整的源代码可以在https://bitbucket.org/radiosity/engine/src
的 filter_kernel cuda_equation / radiance_cuda.cu中找到答案 0 :(得分:1)
我认为这是CPU程序员的大脑变形。在CPU上我期望性能提升,因为消除了分支和分支错误预测惩罚。
但GPU上没有分支预测且没有惩罚,所以只有指令才算重要。
首先,我需要将代码重写为简单的代码。
有分支:
int active;
for (int i = 0; i != hit_interaction_count; ++i)
if (i == decision)
active = __ballot(1);
没有分支:
int active = 0;
for (int i = 0; i != hit_interaction_count; ++i)
{
int mask = 0 - (i == decision);
active |= (mask & __ballot(mask));
}
在第一个版本中,有大约3个操作:compare
,if
和__ballot()
。
在第二个版本中,有大约5个操作:compare
,make mask
,__ballot()
,&
和|=
。
共同代码中有大约15个操作。
两个循环运行5个循环。它首先是35个操作,第二个是45个操作。此计算可以解释性能下降。