是的,您的代码中至少有2种不同的比赛条件。
-
由于您正在循环中加载整个共享内存(即,在循环中反复加载所有共享内存),因此有必要使用
__syncthreads()
. 这样做将减少从一次运行到另一次运行的可变性,直至第6位或第7位有效十进制数字,这与
ordinary
float
variability in floating-point operations
,其中操作顺序不重复(此处通常如此)。
添加以下行:
for(x=0; x<SIZE; x++){
__syncthreads(); // add this line
// load mat_2d (on shared memory) using d_mat_3d (on global memory)
for(i=0; i<SIZE*SIZE; i++){
mat_2d[i] = d_mat_3d[i+x*SIZE*SIZE];
}
// sync threads as mat_2d is on shared memory
__syncthreads();
应该基本上纠正这个问题。如果不这样做,当内核循环时
x
,一些扭曲可以“抢先”开始加载共享内存,而以前的扭曲仍忙于中的前一个循环迭代
x个
(注意下面的评论2,这可能会加剧这个问题。)
-
自从
每个螺纹块
正在向整个
d_mat_3d
,当每个threadblock尝试写入不同的值时,您有一个竞争条件。线程块的执行顺序(CUDA未定义)将主要决定最终的结果,这很容易因运行而异。我所知道的在不进行完整内核重写的情况下解决这个问题的唯一简单方法是只启动1个threadblock(它仍然会填充
d\U mat\U 3d
). 这种竞争条件是一种全局内存竞争
cuda-memcheck
目前无法发现此类种族。我不想读太多,但这段代码实际上没有任何意义,要么表示对合理的代码缺乏关注,要么表示对CUDA执行模型缺乏了解(尤其是与下面的第2项结合在一起)
我还想指出一些其他的事情。
-
您对的使用
__syncthreads()
在最后一个线程块中可能非法。此构造:
if(n >= N_ELEM)
return;
将允许(最后一个)线程块中的某些线程提前退役,这意味着它们将不参与后续的
__syncthreads()
声明。这在CUDA中是非法的,该限制包含在
the programming guide
. 这可以通过删除早期返回并使用
if (n < N_ELEM)
或类似。
-
正如您已经在评论中指出的那样,您的内核代码通常很奇怪。这方面的一个例子是,您让块中的每个线程执行与共享内存完全相同的加载和存储。从几个方面来看,这是浪费性能的。
我并不是说这涵盖了代码的所有问题,仅仅是我注意到的事情。这是一个相对完整的测试用例,我用它来验证我的发现。它包括对我上面提到的项目的一些更改,以及对我来说很重要的其他各种更改:
$ cat t268.cu
#include <cstdlib>
#include <sstream>
#include <cstdio>
#include <cuda.h>
#include <cuda_runtime_api.h>
#define W 7 // fix limit for loops in kernel
#define SIZE 100 // defining matrix dimension
#define N_ELEM 10000 // no of elements in each vector
#define NTPB 1024 // no of threads per block
using namespace std;
__device__ float d_mat_3d[SIZE*SIZE*SIZE];
__global__ void cuda_kernel(float *d_A, float *d_v){
__shared__ float mat_2d[SIZE*SIZE]; // a 2D slice of 3D matrix d_mat_3d
unsigned int n = blockDim.x*blockIdx.x+threadIdx.x;
int x, y, z, i;
float r;
float A = d_A[n];
float v = d_v[n];
#pragma unroll
for(x=0; x<SIZE; x++){
__syncthreads();
if (n < N_ELEM){
// load mat_2d (on shared memory) using d_mat_3d (on global memory)
for(i=0; i<SIZE*SIZE; i++){
mat_2d[i] = d_mat_3d[i+x*SIZE*SIZE];
}
}
// sync threads as mat_2d is on shared memory
__syncthreads();
if (n < N_ELEM){
for(y=SIZE/2; y<SIZE/2+W; y++){
for(z=SIZE/2; z<SIZE/2+W; z++){
r = sqrt( pow(A,2) / v ); // no need to be in these loops. I know, but for my real case, it must be.
atomicAdd(&(mat_2d[z+y*SIZE]), r); // atomically add r
}
}
}
__syncthreads();
// write mat_2d (shared memory) back to mat_3d (global memory)
if (n < N_ELEM){
for(i=0; i<SIZE*SIZE; i++){
d_mat_3d[i+x*SIZE*SIZE] = mat_2d[i];
}
}
}
}
// this function writes h_mat_3d to disk.
void write_image(float *h_mat_3d){
for (int i = 0; i < SIZE*SIZE; i++){
for (int j = 0; j < SIZE; j++)
if (h_mat_3d[i*SIZE+j] > 1.0f) printf("%d:%f\n ", i*SIZE+j, h_mat_3d[i*SIZE+j]);
printf("\n");}
}
int main(){
int i;
float *h_A = new float[N_ELEM]; // some large vector
float *h_v = new float[N_ELEM]; // some other large vector
float *h_mat_3d = new float[SIZE*SIZE*SIZE]; // will be filled w/ 0
float *d_A; // device variables
float *d_v;
for(i=0; i<N_ELEM; i++){
h_A[i] = 0.2f+i/(float)N_ELEM; // fill out with some calculations
h_v[i] = 0.5f+2.f*i/(float)N_ELEM;
}
for(i=0; i<SIZE*SIZE*SIZE; i++){
h_mat_3d[i] = 0.f; // fill h_mat_3d with 0
}
cudaMalloc((void **)&d_A, sizeof(float)*N_ELEM); // allocate variables on device
cudaMalloc((void **)&d_v, sizeof(float)*N_ELEM);
cudaMemcpy(d_A, h_A, sizeof(float)*N_ELEM, cudaMemcpyHostToDevice); // copy from host to device
cudaMemcpy(d_v, h_v, sizeof(float)*N_ELEM, cudaMemcpyHostToDevice);
cudaMemcpyToSymbol(d_mat_3d, h_mat_3d, sizeof(float)*SIZE*SIZE*SIZE); // copy h_mat_3d to device
cuda_kernel<<<1,NTPB>>>(d_A, d_v); // execute kernel
cudaMemcpyFromSymbol(h_mat_3d, d_mat_3d, sizeof(float)*SIZE*SIZE*SIZE); // write it back to h_mat_3d
write_image(h_mat_3d); // write h_mat_3d to disk for checking
cudaFree(d_A); // free memory
delete [] h_A;
delete [] h_v;
return 0;
}
$ nvcc -arch=sm_52 -o t268 t268.cu
$ ./t268 > out1.txt
$ ./t268 > out2.txt
$ diff out1.txt out2.txt |more
51,57c51,57
< 5050:330.657715
< 5051:330.657715
< 5052:330.657715
< 5053:330.657715
< 5054:330.657715
< 5055:330.657715
< 5056:330.657715
---
> 5050:330.657654
> 5051:330.657593
> 5052:330.657593
> 5053:330.657593
> 5054:330.657593
> 5055:330.657593
> 5056:330.657593
59,65c59,65
< 5150:330.657715
< 5151:330.657715
< 5152:330.657715
< 5153:330.657715
< 5154:330.657745
< 5155:330.657745
< 5156:330.657745
---
> 5150:330.657593
> 5151:330.657593
> 5152:330.657593
> 5153:330.657593
> 5154:330.657593
> 5155:330.657593
> 5156:330.657593
67,73c67,73
< 5250:330.657745
< 5251:330.657745
< 5252:330.657745
< 5253:330.657745
< 5254:330.657715
< 5255:330.657715
< 5256:330.657715
---
> 5250:330.657593
> 5251:330.657593
> 5252:330.657623
> 5253:330.657593
> 5254:330.657593
> 5255:330.657593
> 5256:330.657593
75,81c75,81
< 5350:330.657715
< 5351:330.657715
< 5352:330.657715
< 5353:330.657715
< 5354:330.657715
< 5355:330.657745
< 5356:330.657715
---
> 5350:330.657593
> 5351:330.657593
$
可以看出,剩余的变化在第7位有效十进制数字中:
51,57c51,57
< 5050:330.657715
...
---
> 5050:330.657654