什么可能导致此并行GPU代码中的“未定义行为”?

时间:2014-02-11 15:21:09

标签: cuda undefined-behavior

让我们假设core1和core2尝试将它们的变量a和b写入相同的内存位置。

如何在这里解释UB?

  • 我们不知道a或b是否写入该内存位置(作为最后一个操作)。
  • 我们甚至不知道那里写的是什么(垃圾)
  • 甚至目标内存地址也可能被误算(段错误?)。
  • 某些逻辑门会产生错误的电流而CPU会自行禁用
  • CPU的频率信息损坏并超频(并自行断开)

我是否可以假设只有第一个选项对所有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吗?

enter image description here

2 个答案:

答案 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)

对于上面的代码,恕我直言,第一个选项是唯一可能的选项。基本上,如果您假设您有足够的线程/处理器来并行执行所有循环,则内部嵌套循环(xy循环)将具有未确定的值。

例如,如果我们只考虑

r += fire_r[j+y][i+x];

部分,fire_r[j+y][i+x]的值可以是原始值,也可以是另一个线程中完成的同一个循环的另一个实例的结果。