在CUDA中使用共享内存而不减少线程

时间:2012-04-23 17:57:17

标签: c cuda shared-memory

看看Mark Harris的缩减示例,我试图看看我是否可以让线程存储中间值而不进行缩减操作:

例如CPU代码:

for(int i = 0; i < ntr; i++)
{
    for(int j = 0; j < pos* posdir; j++)
    {
        val = x[i] * arr[j];
        if(val > 0.0)
        {
            out[xcount] = val*x[i];
            xcount += 1;
        }
    }
}

等效GPU代码:

const int threads = 64; 
num_blocks = ntr/threads;

__global__ void test_g(float *in1, float *in2, float *out1, int *ct, int posdir, int pos)
{
    int tid = threadIdx.x + blockIdx.x*blockDim.x;
    __shared__ float t1[threads];
    __shared__ float t2[threads];

    int gcount  = 0;

    for(int i = 0; i < posdir*pos; i += 32) {
        if (threadIdx.x < 32) {
            t1[threadIdx.x] = in2[i%posdir];
        }
       __syncthreads();

        for(int i = 0; i < 32; i++)
        {
            t2[i] = t1[i] * in1[tid];
                if(t2[i] > 0){
                    out1[gcount] = t2[i] * in1[tid];
                    gcount = gcount + 1;
                }
        }
    }        
    ct[0] = gcount;
}

我在这里要做的是以下步骤:

(1)将32的in2值存储在共享内存变量t1中,

(2)对于i和in1 [tid]的每个值,计算t2 [i],

(3)if t2[i] > 0针对i的特定组合,将t2[i]*in1[tid]写入out1[gcount]

但我的输出都错了。我甚至无法计算t2 [i]大于0的所有时间。

有关如何为每个i和tid保存gcount值的任何建议?在调试时,我发现对于块(0,0,0)和线程(0,0,0),我可以顺序地看到更新的t2的值。在CUDA内核将焦点切换到块(0,0,0)和线程(32,0,0)之后,再次重写out1 [0]的值。如何为每个线程获取/存储out1的值并将其写入输出?

到目前为止,我尝试了两种方法:(由@paseolatis在NVIDIA论坛上建议)

(1)定义offset=tid*32; and replace out1[gcount] with out1[offset+gcount]

(2)定义

__device__ int totgcount=0; // this line before main()
atomicAdd(&totgcount,1);
out1[totgcount]=t2[i] * in1[tid];

int *h_xc = (int*) malloc(sizeof(int) * 1);
cudaMemcpyFromSymbol(h_xc, totgcount, sizeof(int)*1, cudaMemcpyDeviceToHost);
printf("GPU: xcount = %d\n", h_xc[0]); // Output looks like this: GPU: xcount = 1928669800

有什么建议吗?提前致谢 !

2 个答案:

答案 0 :(得分:2)

好的,让我们比较您对代码应该做什么的描述与您发布的内容(有时称为rubber duck debugging)。

  1. 在共享内存变量t1

    中存储32个in2值

    你的内核包含这个:

    if (threadIdx.x < 32) {
        t1[threadIdx.x] = in2[i%posdir];
    }
    

    有效地将in2中的相同值加载到t1的每个值中。我怀疑你想要更像这样的东西:

    if (threadIdx.x < 32) {
        t1[threadIdx.x] = in2[i+threadIdx.x];
    }
    
  2. 对于i和in1[tid]的每个值,请计算t2[i]

    这部分没问题,但为什么共享内存中需要t2呢?它只是一个中间结果,可以在内部迭代完成后丢弃。你可以很容易地得到类似的东西:

    float inval = in1[tid];
    .......
    for(int i = 0; i < 32; i++)
    {
         float result = t1[i] * inval;
         ......
    
  3. 如果对于i的特定组合t2[i] > 0,请写 t2[i]*in1[tid]out1[gcount]

    这就是问题真正开始的地方。在这里你这样做:

            if(t2[i] > 0){
                out1[gcount] = t2[i] * in1[tid];
                gcount = gcount + 1;
            }
    

    这是一场记忆竞赛。 gcount是一个线程局部变量,因此每个线程将在不同的时间用自己的值覆盖任何给定的out1[gcount]。为了使这段代码能够正常工作,您必须拥有的是将gcount作为全局内存变量并使用原子内存更新来确保每个线程每次都使用唯一值gcount输出一个值。但是请注意,如果经常使用原子内存访问是非常昂贵的(这就是我在评论中询问每个内核启动时有多少输出点的原因。)

  4. 生成的内核可能如下所示:

    __device__ int gcount; // must be set to zero before the kernel launch
    
    __global__ void test_g(float *in1, float *in2, float *out1, int posdir, int pos)
    {
        int tid = threadIdx.x + blockIdx.x*blockDim.x;
        __shared__ float t1[32];
    
        float ival = in1[tid];
    
        for(int i = 0; i < posdir*pos; i += 32) {
            if (threadIdx.x < 32) {
                t1[threadIdx.x] = in2[i+threadIdx.x];
            }
            __syncthreads();
    
            for(int j = 0; j < 32; j++)
            {
                float tval = t1[j] * ival;
                if(tval > 0){
                    int idx = atomicAdd(&gcount, 1);
                    out1[idx] = tval * ival
                }
            }
        }        
    }
    

    免责声明:用浏览器编写,从未编译或测试过,使用风险自负......

    请注意,您对ct的写入也是内存竞争,但gcount现在是一个全局值,您可以在内核之后读取值而无需ct


    编辑:在运行内核之前,您似乎遇到了将gcount置零的问题。为此,您需要使用cudaMemcpyToSymbolcudaGetSymbolAddresscudaMemset之类的内容。它可能看起来像:

    const int zero = 0;
    cudaMemcpyToSymbol("gcount", &zero, sizeof(int), 0, cudaMemcpyHostToDevice);
    

    同样,通常的免责声明:用浏览器编写,从未编译或测试过,使用风险自负......

答案 1 :(得分:1)

更好的方法是做你正在做的事情是给每个线程自己的输出,并让它增加自己的count并输入值 - 这样,双for循环可以在任何地方并行发生顺序,这是GPU做得好的。输出是错误的,因为线程共享out1数组,所以它们都会覆盖它。

您还应该将代码复制到共享内存中,移动到一个单独的循环中,然后使用__syncthreads()。如果__syncthreads()退出循环,你应该会获得更好的性能 - 这意味着你的共享数组必须是in2的大小 - 如果这是一个问题,最后有一个更好的方法来解决这个问题。这个答案。

您还应将threadIdx.x < 32支票移至外面。所以你的代码看起来像这样:

if (threadIdx.x < 32) {
    for(int i = threadIdx.x; i < posdir*pos; i+=32) {
        t1[i] = in2[i];
    }
}
__syncthreads();

for(int i = threadIdx.x; i < posdir*pos; i += 32) {
    for(int j = 0; j < 32; j++)
    {
         ...
    }
}

然后将__syncthreads()gcount += count的原子添加,以及本地输出数组的副本添加到全局 - 这部分是顺序的,并且会损害性能。如果可以的话,我会为每个本地数组提供一个指向数组指针的全局列表,并将它们放在CPU上。

另一个变化是你不需要t2的共享内存 - 它对你没有帮助。而你这样做的方式,似乎只有在使用单个块时才有效。要从大多数NVIDIA GPU中获得良好性能,您应该将其划分为多个块。您可以根据共享内存约束来定制它。当然,块之间没有__syncthreads(),因此每个块中的线程必须遍历内部循环的整个范围,以及外部循环的分区。