我在CUDA中循环展开时遇到了一些问题。
在普通的串行代码中:
//serial basic:
for(int i = 0; i < n; i++){
c[i] = a[i] + b[i];}
//serial loop unroll:
for(int i = 0; i < n/4; i++){
c[i] = a[i] + b[i];
c[i+1] = a[i+1] + b[i+1];
c[i+2] = a[i+2] + b[i+2];
c[i+3] = a[i+3] + b[i+3];}
所以我认为CUDA循环展开看起来像这样:
int i = 2*(threadIdx.x + blockIdx.x * gridDim.x);
a[i+0] = b[i+0] + c[i+0];
a[i+1] = b[i+1] + c[i+1];
但在CUDA手册中展开的例子我无法理解
这是一个普通的GlobalWrite内核:
__global__ void GlobalWrites( T *out, T value, size_t N )
{
for(size_t i = blockIdx.x*blockDim.x+threadIdx.x;
i < N;
i += blockDim.x*gridDim.x ) {
out[i] = value;
}
}
展开内核:
template<class T, const int n> __global__ void Global_write(T* out, T value, size_t N){
size_t i;
for(i = n*blockDim.x*blockIdx.x + threadIdx.x;
i < N - n*blockDim.x*blockIdx.x;
i += n*gridDim.x*blockDim.x;)
for(int j = 0; j < n; i++){
size_t index = i + j * blockDim.x;
outp[index] = value;
}
for ( int j = 0; j < n; j++ ) {
size_t index = i+j*blockDim.x;
if ( index<N ) out[index] = value;
}}
我知道这个内核使用更少的块但可能有人解释为什么它更好地工作(n = 4,10%加速)。
答案 0 :(得分:6)
如果不明显,因为n
是模板参数,所以它在编译时是常量。这意味着编译器可以通过展开自由地优化常量跳闸计数循环。因此,删除模板魔术并手动展开循环以获得您提到的n = 4案例是有益的:
template<class T>
__global__ void Global_write(T* out, T value, size_t N)
{
size_t i;
for(i = 4*blockDim.x*blockIdx.x + threadIdx.x;
i < N - 4*blockDim.x*blockIdx.x;
i += 4*gridDim.x*blockDim.x;) {
out[i + 0 * blockDim.x] = value;
out[i + 1 * blockDim.x] = value;
out[i + 2 * blockDim.x] = value;
out[i + 3 * blockDim.x] = value;
}
if ( i+0*blockDim.x < N ) out[i+0*blockDim.x] = value;
if ( i+1*blockDim.x < N ) out[i+1*blockDim.x] = value;
if ( i+2*blockDim.x < N ) out[i+2*blockDim.x] = value;
if ( i+3*blockDim.x < N ) out[i+3*blockDim.x] = value;
}
展开的内部循环产生四个完全独立的写入,这些写入被合并。正是这种指令级并行性使代码具有更高的指令吞吐量和更高的性能。如果您还没有看过,我强烈推荐几年前GTC会议上的Vasily Volkov Unrolling Parallel Loops。他的演讲列出了为什么这种类型的循环展开是CUDA优化的理论背景。
答案 1 :(得分:3)
在模板化内核中,const int n
在编译时是已知的,允许编译器实际展开for(int j = 0; j < n; i++)
循环,从而删除该循环上的条件检查。如果在编译时未知循环大小,则编译器无法展开循环。就这么简单。