看一下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)在共享内存变量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)定义
__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有什么建议吗?提前感谢!
发布于 2012-04-24 04:40:07
好的,让我们将您对代码应该做什么的描述与您发布的内容进行比较(这有时称为rubber duck debugging)。
t1中您的内核包含以下内容:
if (threadIdx.x < 32) { t1threadIdx.x = in2i%posdir;}
它有效地将相同的值从in2加载到t1的每个值中。我怀疑你想要更多这样的东西:
如果(threadIdx.x < 32) { t1threadIdx.x = in2i+threadIdx.x;}
in1[tid]的每个值,计算t2[i],这部分是可以的,但是为什么共享内存中需要t2呢?它只是一个中间结果,在内部迭代完成后可以丢弃。你可以很容易地拥有这样的东西:
float inval = in1tid;.......对于(int i = 0;i< 32;i++) { float result = t1i * inval;……
t2[i] > 0对于i的特定组合,将t2[i]*in1[tid]写入out1[gcount]这才是问题真正开始的地方。您可以在此处执行以下操作:
if(t2i > 0){ out1gcount = t2i * in1tid;gcount = gcount + 1;}
这是一场记忆竞赛。gcount是一个线程局部变量,因此每个线程将在不同的时间用它自己的值覆盖任何给定的out1[gcount]。要使此代码在编写时正确工作,您必须拥有gcount作为全局内存变量,并使用原子内存更新来确保每个线程每次输出一个值时都使用gcount的唯一值。但是需要注意的是,如果经常使用原子内存访问是非常昂贵的(这就是为什么我在评论中询问每次内核启动有多少输出点)。
生成的内核可能如下所示:
__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置零的问题。为此,您需要使用诸如cudaMemcpyToSymbol或cudaGetSymbolAddress和cudaMemset之类的东西。它可能看起来像这样:
const int zero = 0;
cudaMemcpyToSymbol("gcount", &zero, sizeof(int), 0, cudaMemcpyHostToDevice);同样,通常的免责声明:在浏览器中编写,从未编译或测试,使用风险自负.....
发布于 2012-04-24 04:14:01
更好的方法是为每个线程提供自己的输出,并让它递增自己的count并输入值-这样,double-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(),所以每个块中的线程必须遍历内部循环的整个范围,以及外部循环的一个分区。
https://stackoverflow.com/questions/10285718
复制相似问题