首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在CUDA中使用共享内存而不减少线程

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

Stack Overflow用户
提问于 2012-04-24 01:57:17
回答 2查看 872关注 0票数 1

看一下Mark Harris的约简示例,我想看看是否可以让线程存储中间值,而不需要约简操作:

例如CPU代码:

代码语言:javascript
复制
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代码:

代码语言:javascript
复制
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)在共享内存变量t1中存储32个in2的值,

(2)对于i和in1tid的每个值,计算t2i,

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

但是我的输出全错了。我甚至不能得到t2i大于0的所有次数的计数。

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

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

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

(2)定义

代码语言:javascript
复制
__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

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

EN

回答 2

Stack Overflow用户

发布于 2012-04-24 04:40:07

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

  1. 将32个in2值存储在共享内存变量t1

您的内核包含以下内容:

if (threadIdx.x < 32) { t1threadIdx.x = in2i%posdir;}

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

如果(threadIdx.x < 32) { t1threadIdx.x = in2i+threadIdx.x;}

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

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

float inval = in1tid;.......对于(int i = 0;i< 32;i++) { float result = t1i * inval;……

  • if t2[i] > 0对于i的特定组合,将t2[i]*in1[tid]写入out1[gcount]

这才是问题真正开始的地方。您可以在此处执行以下操作:

if(t2i > 0){ out1gcount = t2i * in1tid;gcount = gcount + 1;}

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

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

代码语言:javascript
复制
__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之类的东西。它可能看起来像这样:

代码语言:javascript
复制
const int zero = 0;
cudaMemcpyToSymbol("gcount", &zero, sizeof(int), 0, cudaMemcpyHostToDevice);

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

票数 2
EN

Stack Overflow用户

发布于 2012-04-24 04:14:01

更好的方法是为每个线程提供自己的输出,并让它递增自己的count并输入值-这样,double-for循环可以以任何顺序并行发生,这是GPU擅长的。输出是错误的,因为线程共享out1数组,所以它们都会覆盖该数组。

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

您还应该将threadIdx.x < 32检查移到外部。因此,您的代码将如下所示:

代码语言:javascript
复制
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(),所以每个块中的线程必须遍历内部循环的整个范围,以及外部循环的一个分区。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/10285718

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档