让我们假设core1和core2尝试将它们的变量a和b写入相同的内存位置。
如何在这里解释UB?
我是否可以假设只有第一个选项对所有CPU(和GPU)供应商都有效?
我刚刚将下面的代码转换为并行GPU代码,它似乎工作正常。
通用代码:
for (j=0; j<YRES/CELL; j++) // this is parallelized
for (i=0; i<XRES/CELL; i++) // this is parallelized
{
r = fire_r[j][i];
g = fire_g[j][i];
b = fire_b[j][i];
if (r || g || b)
for (y=-CELL; y<2*CELL; y++)
for (x=-CELL; x<2*CELL; x++)
addpixel(i*CELL+x, j*CELL+y, r, g, b, fire_alpha[y+CELL][x+CELL]);
//addpixel accesses neighbour cells' informations and writes on them
//and makes UB
r *= 8;
g *= 8;
b *= 8;
for (y=-1; y<2; y++)
for (x=-1; x<2; x++)
if ((x || y) && i+x>=0 && j+y>=0 && i+x<XRES/CELL && j+y<YRES/CELL)
{
r += fire_r[j+y][i+x];
g += fire_g[j+y][i+x];
b += fire_b[j+y][i+x];
}
r /= 16;
g /= 16;
b /= 16;
fire_r[j][i] = r>4 ? r-4 : 0; // UB
fire_g[j][i] = g>4 ? g-4 : 0; // UB
fire_b[j][i] = b>4 ? b-4 : 0;
}
的OpenCL:
" int i=get_global_id(0); int j=get_global_id(1);"
" int VIDXRES="+std::to_string(kkVIDXRES)+";"
" int VIDYRES="+std::to_string(kkVIDYRES)+";"
" int XRES="+std::to_string(kkXRES)+";"
" int CELL="+std::to_string(kkCELL)+";"
" int YRES="+std::to_string(kkYRES)+";"
" int x=0,y=0,r=0,g=0,b=0,nx=0,ny=0;"
" r = fire_r[j*(XRES/CELL)+i];"
" g = fire_g[j*(XRES/CELL)+i];"
" b = fire_b[j*(XRES/CELL)+i];"
" int counterx=0;"
" if (r || g || b)"
" for (y=-CELL; y<2*CELL; y++){"
" for (x=-CELL; x<2*CELL; x++){"
" addpixel(i*CELL+x, j*CELL+y, r, g, b, fire_alpha[(y+CELL)*(3*CELL)+(x+CELL)],vid,vido);"
" }}"
" r *= 8;"
" g *= 8;"
" b *= 8;"
" for (y=-1; y<2; y++){"
" for (x=-1; x<2; x++){"
" if ((x || y) && i+x>=0 && j+y>=0 && i+x<XRES/CELL && j+y<YRES/CELL)"
" {"
" r += fire_r[(j+y)*(XRES/CELL)+(i+x)];"
" g += fire_g[(j+y)*(XRES/CELL)+(i+x)];"
" b += fire_b[(j+y)*(XRES/CELL)+(i+x)];"
" }}}"
" r /= 16;"
" g /= 16;"
" b /= 16;"
" fire_r[j*(XRES/CELL)+i] = (r>4 ? r-4 : 0);"
" fire_g[j*(XRES/CELL)+i] = (g>4 ? g-4 : 0);"
" fire_b[j*(XRES/CELL)+i] = (b>4 ? b-4 : 0);"
这是2D NDrangeKernel的局部边界UB的一些罕见伪影的图片。这些可以杀死我的GPU吗?
答案 0 :(得分:4)
在xf86和xf86_64架构上,它意味着我们不知道是否将a或b写入该内存位置(作为最后一个操作),因为加载/存储操作为32(对于两者)或64 bit(仅限xf86_64)内存对齐数据类型是原子的。
在其他架构上通常我们甚至不知道那里写的是什么(垃圾)是一个有效的答案 - 当然在RISC架构上,我目前在GPU上并不知道。
请注意,代码工作的事实并不意味着它是正确的,并且在99%的时间里,它是句子的来源,例如“有编译器错误,代码工作到之前的版本”或“代码适用于开发机器。选择用于生产的服务器已损坏“:)
编辑:
在NVidia GPU上,我们的内存模型排序很弱。在description on the Cuda C Programming guide中没有明确声明存储操作是原子操作。写操作来自同一个线程,因此并不意味着加载/存储操作是原子的。
答案 1 :(得分:1)
对于上面的代码,恕我直言,第一个选项是唯一可能的选项。基本上,如果您假设您有足够的线程/处理器来并行执行所有循环,则内部嵌套循环(x
和y
循环)将具有未确定的值。
例如,如果我们只考虑
r += fire_r[j+y][i+x];
部分,fire_r[j+y][i+x]
的值可以是原始值,也可以是另一个线程中完成的同一个循环的另一个实例的结果。