opencl最佳组大小

时间:2013-08-07 13:53:24

标签: opencl

我在OpenCL上运行mandelbrot生成器(来自静态参数的2D图像)。 该计划很简单:

__kernel
void mandelbrot(__global uchar * output, 
                const float xstep,
                const float xoffset,
                const float ystep,
                const float yoffset,
                const int maxiter)
{
    int gid_y = get_global_id(1);
    int gid_x = get_global_id(0);

    //calculate x and y on the fly for every pixel. 
    //This is just as fast as reading precalculated rulers from global memory.
    float x = gid_x * xstep + xoffset;
    float y = gid_y * ystep + yoffset;

    float real = 0;
    float imag = 0;

    int out = 0;

    for(int curiter = 0; curiter < maxiter; curiter++) {
        float nreal = real*real - imag*imag + x;
        imag = 2* real*imag + y;
        real = nreal;

        if (real*real + imag*imag > 4.0f) {
            out = curiter;
            break;
        }
    }

    //normalize output
    out *= 256.0 / (float)maxiter;
    output[gid_y * get_global_size(0) + gid_x] = out;

}

[编辑] [已发布完整内核,并按建议交换行和列。这样我在AMD上获得了18%的表现,但在NVidia上获得了0%的表现。原始代码是

output[get_global_id(0) * get_global_size(1) + get_global_id(1)] = out;

[/编辑]

我在我的Nvidia Quadro 1000M上运行它,它有2个计算单元和96个CUDA核心(每个计算单元48个核心)。

我在排队内核时更改本地组大小。这些是我在生成400万像素图像时获得的不同尺寸的性能结果。 所有数字都来自OpenCL分析器,并将最终内存副本排除回操作系统。 图像为40992x10272 - 高度和宽度均可被48整除。

rows x columns
8x8: 397 MPixel/s
8x12: 505 MPixel/s
8x16: 523 MPixel/s
8x24: 521 MPixel/s
8x32: 520 MPixel/s
8x48: 520 MPixel/s

1x48: 321 MPixel/s
2x32: 424 MPixel/s
2x48: 523 MPixel/s
4x24: 519 MPixel/s
3x32: 525 MPixel/s
4x32: 525 MPixel/s
4x48: 525 MPixel/s

12x8: 490 MPixel/s
12x12:464 MPixel/s
12x24:505 MPixel/s
12x32:508 MPixel/s
12x48:433 MPixel/s

16x8: 499 MPixel/s
16x12:499 MPixel/s
16x16:472 MPixel/s
16x24:450 MPixel/s
16x32:440 MPixel/s
16x48:418 MPixel/s

其中一些数字让我感到困惑。 虽然很清楚为什么我用48列获得最佳结果(感谢SIMD操作如何工作),但我不明白:

  1. 当我每组使用16行时,为什么性能会急剧下降?
  2. 为什么我的1x48表现不佳?
  3. 为什么在天堂我能获得3x32,4x32和8x32的顶级性能?!?我原以为33%的SIMD处理器处于空闲状态,而工作组看起来就像是在两个计算单元之间??
  4. 为什么PREFERRED_WORK_GROUP_SIZE_MULTIPLE返回32而不是48?
  5. 是否有一种非实证的方法可以在任何GPU(ATI / Nvidia / Intel HD)上找出最佳性能的几何结构,只考虑我从OpenCL信息结构中获得的内容?
  6. 提前致谢

3 个答案:

答案 0 :(得分:16)

我回答了类似的问题here,在阅读以下内容之前,您可能会感兴趣。

当我每组使用16行时,为什么性能会急剧下降?

实际上,当你使用12行时,它已经降级了。内存访问按事务处理。事务将一次性获取一定数量的字节。现在,如果多个工作项尝试访问数组中的几个连续元素,则意味着一个事务可能足以为它们提供服务。

因为您以这种方式访问​​内存:

output[get_global_id(0) * get_global_size(1) + get_global_id(1)] = out;

这意味着本地大小在维度0中越大,事务的数量就越大,因为您必须访问非连续元素(由get_global_size(1)元素分隔)。全局内存访问非常昂贵。

因此,对于12/16行,您至少需要12/16笔交易。这导致了你的第二个问题:

为什么1x48会导致性能不佳?

基于我之前刚才所说的,似乎性能应该很好,因为交易次数会很少。

但是这里出现了空转线程的问题。关于每个SM的48个核心所获得的信息是错误的,正如其他人已经指出的那样。在NVIDIA硬件上,线程在32组(在NVIDIA中称为warp)中执行。请注意,这些组称为wavefront,AMD最多可以有64个线程。由于在这种情况下您有一个由48个线程(1乘48)组成的工作组,这意味着计划了64个线程。由于您无法执行一小部分扭曲,因此它总是计划多个32个线程。

因此,在这种情况下,您有四分之一的线程什么都不做。实际上,当您与2x32(仍为64个线程 - 2个经线,但已充分利用)获得的结果进行比较时,321 MPixel / s几乎是424 MPixel / s的3/4。

值得注意的是这个结果: 2x48:523 MPixel / s 。在这种情况下,您的工作组大小为96,是32的倍数。因此没有空闲线程。

为什么在天堂我能获得3x32,4x32和8x32的顶级性能?!

嗯,答案来自前两个:你使用32的倍数,并保持维度0中的线程数相对较小。但是,让我们仔细研究一下您的结果:

2x32:  424 MPixel/s
3x32:  525 MPixel/s
4x32:  525 MPixel/s
8x32:  520 MPixel/s
16x32: 440 MPixel/s

最后两行的性能下降很容易用上述内容解释。但是,第一行和第二行之间的性能提升不是。

在这种情况下,性能的提升来自其他地方。在第二种情况下,足够的warp在相同的SM 上运行以隐藏访问内存延迟。您会看到REFERRED_WORK_GROUP_SIZE_MULTIPLE值仅表示您应尝试使用此值的MULTIPLE以获得最佳性能。 可以在同一个SM上同时安排几个warp。

那么,它是如何运作的?让我们来看看3x32。您有一个由3个warp组成的工作组。由于它们属于同一工作组,因此它们按照OpenCL标准的要求安排在相同的SM上(如果不是这种情况,则工作组内的线程之间的同步是不可能的)。

第一个warp开始运行,直到它停止,因为需要内存访问。同时warp 1等待内存事务完成,warp 2可以开始运行。由于SM上有很多寄存器,因此SM可以轻松快速地切换上下文以运行其他warp。 warp 1的所有变量都保留在分配给warp 1的寄存器上。然后warp 2命中了需要内存访问的行并停止。此时,下一个准备运行warp 可以开始运行。如果内存访问完成,它可能是warp 3,也可能是warp 1。在你的情况下,它似乎是warp 3运行,因为你有2x32和3x32之间的差异。在第一种情况下,没有足够的warp被安排隐藏内存访问,但在第二种情况下有。

事实上,这个影响以及问题2中1x48尺寸的糟糕表现。

为什么PREFERRED_WORK_GROUP_SIZE_MULTIPLE返回32而不是48?

已经回答。

是否有一种非实证的方法可以在任何GPU(ATI / Nvidia / Intel HD)上找出最佳性能的几何结构,只考虑我从OpenCL信息结构中获得的内容?

与任何其他语言一样。当你知道它是如何工作的时候,它可以帮助你产生良好的第一代码。但是你仍然需要对它进行基准测试,并经历一个试验和错误的过程来调整它。记住我刚才所写的内容只是对性能至关重要的一小部分内容。从OpenCL查询一些信息并结合对CPU / GPU的良好理解显然会有所帮助......但这就是它。

因为影响表现的很多参数都是对手,所以你在另一方获得的东西会在另一方面失去。

因此请保持基准;)

答案 1 :(得分:1)

这一切都取决于你没有展示的代码。这是关键。 如果您的代码非常简单,即:out = 8;那么您的假设可能是正确的。

但是,正如您所说,值REFERRED_WORK_GROUP_SIZE_MULTIPLE返回32.这意味着,32是计算单元可以并行启动而不影响性能的最大并发线程数。 例如,启动超过32没有任何意义。如果使用32,你已经耗尽了本地内存,你需要重新使用全局内存(这非常慢)。

如果你试图超过推荐的限制,你就可以获得 - >性能下降。不是32更好,是对面。 48不好。

我建议你:

  1. 如果可能,请使用自动大小(将null作为本地大小传递给内核)。如果您不担心本地工作形状,这将带来最佳性能。
  2. 如果需要手动设置本地大小,请使用REFERRED_WORK_GROUP_SIZE_MULTIPLE作为参考。

答案 2 :(得分:0)

内核访问全局内存的方式至关重要,并由工作组和全局维度决定:

  • 同一工作组中的连续工作项将写入哪些地址?这里的步幅是get_global_size(1),您可能想要交换X和Y.通常可以更快地处理连续工作项中的连续元素。这是最重要的因素。

  • 连续工作组将写入哪些地址?连续工作组将经常在不同的计算单元上同时安排。他们可能最终竞争相同的渠道/银行,导致业绩下降。

  • 通常最好写32位整数而不是字节。

为了最大限度地提高性能,我建议您引入更多按钮:在单个工作项内编写内核计算几个像素的块(例如4x2),然后对(块大小)x的所有组合进行基准测试(工作 - 组大小)x(XY交换)x(图像大小)。然后选择最适合你的GPU。