(有关设备(全局)内存数组的类似问题,例如my own question。)
假设我有一个像这样的CUDA内核代码:
my_arr[MyCompileTimeConstant];
/* ... */
for(unsigned i = 0; i < foo(); i++) {
my_arr[bar(i)] += some_value;
}
现在,我想在开始添加其条目之前将my_arr
初始化为全零。我能做的比琐碎的循环更好吗
for(unsigned i = 0; i < MyCompileTimeConstant; i++) {
my_arr[i] = 0;
}
注意:我特意在编译时知道了循环范围和数组大小常量。如果它们在运行时通过,问题会略有不同。当然,它可能不会改变CUDA的答案,就像它在CPU上运行的代码一样
答案 0 :(得分:3)
一个简单的循环应该是&#34;最好的&#34;方法(但见下面的最终评论)。以下面的内核为例:
template<int version>
__global__
void tkernel(int *A, int *B, int *C, int n)
{
int biglocal[100];
switch(version) {
case 1:
for(int i=0; i<100; i++) {
biglocal[i] = 0;
};
break;
case 2:
memset(&biglocal[0], 0, 100*sizeof(int));
break;
case 3:
const int4 zero = {0, 0, 0, 0};
int4 *p = reinterpret_cast<int4*>(&biglocal[0]);
#pragma unroll
for(int i=0; i<100/4; i++) {
p[i] = zero;
}
break;
}
if (n>0) {
for(int i=0; i<100; i++) {
biglocal[A[threadIdx.x*i]] += B[threadIdx.x*i];
}
C[threadIdx.x] = biglocal[n];
}
}
template __global__ void tkernel<1>(int *, int *, int *, int);
template __global__ void tkernel<2>(int *, int *, int *, int);
template __global__ void tkernel<3>(int *, int *, int *, int);
这里我们有三种不同的方法可以将大型本地存储器阵列归零,还有一些代码可以说服编译器整个初始化序列和本地阵列不应该被优化掉。
使用CUDA 6版本编译器查看为计算2.1目标发出的PTX,版本1和版本1都是3看起来像这样:
.local .align 4 .b8 __local_depot0[400];
.reg .b64 %SP;
.reg .b64 %SPL;
.reg .pred %p<3>;
.reg .s32 %r<67>;
.reg .s64 %rd<73>;
mov.u64 %SPL, __local_depot0;
ld.param.u64 %rd4, [_Z7tkernelILi1EEvPiS0_S0_i_param_0];
ld.param.u64 %rd5, [_Z7tkernelILi1EEvPiS0_S0_i_param_1];
ld.param.u64 %rd6, [_Z7tkernelILi1EEvPiS0_S0_i_param_2];
ld.param.u32 %r21, [_Z7tkernelILi1EEvPiS0_S0_i_param_3];
add.u64 %rd7, %SPL, 0;
mov.u32 %r66, 0;
st.local.u32 [%rd7], %r66;
st.local.u32 [%rd7+4], %r66;
st.local.u32 [%rd7+8], %r66;
st.local.u32 [%rd7+12], %r66;
st.local.u32 [%rd7+16], %r66;
st.local.u32 [%rd7+20], %r66;
// etc
即。编译器展开循环并发出一串32位存储指令。版本3中的int4
技巧产生了与简单循环相同的代码,这有点令人惊讶。然而,版本2得到了这个:
.local .align 4 .b8 __local_depot1[400];
.reg .b64 %SP;
.reg .b64 %SPL;
.reg .pred %p<4>;
.reg .s16 %rs<2>;
.reg .s32 %r<66>;
.reg .s64 %rd<79>;
mov.u64 %SPL, __local_depot1;
ld.param.u64 %rd7, [_Z7tkernelILi2EEvPiS0_S0_i_param_0];
ld.param.u64 %rd8, [_Z7tkernelILi2EEvPiS0_S0_i_param_1];
ld.param.u64 %rd9, [_Z7tkernelILi2EEvPiS0_S0_i_param_2];
ld.param.u32 %r21, [_Z7tkernelILi2EEvPiS0_S0_i_param_3];
add.u64 %rd11, %SPL, 0;
mov.u64 %rd78, 0;
BB1_1:
add.s64 %rd12, %rd11, %rd78;
mov.u16 %rs1, 0;
st.local.u8 [%rd12], %rs1;
add.s64 %rd78, %rd78, 1;
setp.lt.u64 %p1, %rd78, 400;
@%p1 bra BB1_1;
即。一个执行8位写操作的循环(注释表明简单列表初始化也会产生这种类型的复制循环)。后者将比前者慢得多。除了存储的大小差异之外,展开的写入流完全独立,并且可以以任何顺序发出以保持指令流水线满,并且应该导致更高的指令吞吐量。我不相信在展开的情况下可以击败编译器,并且简单的循环看起来产生与向量化的简单尝试相同的代码。如果你非常热衷,我想你可以尝试使用内联PTX来生成更多的商店。我不知道这样做是否会有任何性能优势。