【问题标题】:How do I best initialize a local-memory array to 0?如何最好地将本地内存数组初始化为 0?
【发布时间】:2019-04-02 21:34:48
【问题描述】:

(关于设备(全局)内存阵列也有类似的问题,例如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;
}

?

注意:我特意让循环范围和数组大小常量在编译时已知。如果它们在运行时通过,问题会略有不同。当然,它可能不会像在 CPU 上运行的代码那样改变 CUDA 的答案

【问题讨论】:

  • 我相信memset 也会起作用,尽管它不是officially documented AFAIK。是不是“更好”,我不知道。
  • @RobertCrovella:和 memcpy 一样,情况会更糟,因为编译器会发出简单的循环来进行字节大小的传输。
  • @talonmies 我不了解 GPU 代码生成,但至少对于 CPU 代码而言,这通常不是真的。编译器识别memcpy 等。对于较小的常量大小,它们与任何普通赋值一样被内联、展开和优化。对于较大的动态大小,运行时库中的实现通常是矢量化的,开销很小(需要检查 alignemnt 并可能手动复制几个字节来修复它)。
  • @delnan:我专门指的是 NVIDIA GPU 编译器,如下所示:stackoverflow.com/a/10468720/681865。这是一个 CUDA 问题,我只是指这个问题。

标签: memory-management cuda initialization zero


【解决方案1】:

一个简单的循环应该是“最好的”方法(但请参阅下面的最终评论)。以如下内核为例:

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 和 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 位写入的循环(cmets 表明简单的列表初始化也会产生这种类型的复制循环)。后者会比前者慢很多。除了存储的大小差异之外,展开的写入流是完全独立的,可以按任何顺序发出,以保持指令流水线满,并应导致更高的指令吞吐量。我不相信在展开的情况下有可能击败编译器,并且一个简单的循环看起来会产生与矢量化的简单尝试相同的代码。如果你真的很热衷,我想你可以尝试内联 PTX 来生成更广泛的商店。我不知道这样做是否会有任何性能优势。

【讨论】:

  • 为了完整起见,我还尝试了初始化列表,例如 'int biglocal[100] = {0};'它给出了与 memset 变体相同的 ptx...
  • @kunzmi:感谢您提供额外的数据点,这对了解很有用。我在反映这一点的问题中编辑了一个注释。大概 memset 样式代码使用 32 位字而不是 8 位字传输?
  • 我重新检查了这个,这取决于你实际做什么。这里的重点是提供的列表不完整。给定的数组成员将被设置为使用循环的方法(使用 32 位字),而未确定的数组成员将使用 8 位字在“memset 样式”中设置为零。和我之前的例子一样,给定的值也是零,第一步被编译器优化掉,只剩下设置为零的部分。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-28
  • 1970-01-01
  • 2017-07-07
  • 1970-01-01
  • 1970-01-01
  • 2017-04-20
  • 1970-01-01
相关资源
最近更新 更多