优化OpenCL缓冲区写入?

时间:2019-08-28 21:31:48

标签: multithreading gpu opencl simd

写入缓冲区时,我遇到了主要瓶颈。

我想做的非常简单。 首先,我正在使用两个全局ID(我正在使用image2d)。每个线程读取9个像素值,即位置(x,y)处的像素及其8个相邻像素,基本上是3x3正方形块。 这项工作由每个线程完成。现在,我计算一些值,并想将每个线程的结果写入输出缓冲区。

每个线程产生64个值,我将它们写入输出缓冲区,这意味着输出缓冲区的大小为(rows * cols * 64)。 我还想支持最多支持640个值的计算,但是显然每个线程都不可能将640个值写入缓冲区,因为需要VRAM。

我必须说线程写入不同的位置,没有覆盖,就是这样 64 *线程数= 64 *全局ID(0)*全局ID(1)= 64 *行*列值。

这是我代码中的主要瓶颈,我的意思是写64个值,我认为这与内存带宽有关,但我不确定。

我该怎么做,以便每个线程可以有效地计算64个值并将其写入输出缓冲区?这不可能吗?

我的GPU是rx 480 4gb,我知道(rows * cols * 64)大小有时可能太大而无法容纳VRAM,但是即使适合,写入速度还是很慢,我认为带宽非常大gpus高吗?

还有其他两个输出缓冲区,但是它们的大小要小得多,因此我们可以忽略它们。

总而言之,这段代码的作用是这样的 1)读取一个9像素的正方形块,其中中间一个为当前值。

2)将8个邻居与当前值相乘,每个像素得到8个值。

3)将8个邻居写入邻居缓冲区。

4)将8 * 8值写入Rx缓冲区。该缓冲区“模拟” x_ * x_ ^ T结果,即相邻值的(8x1)x(1x8)矩阵乘积。

请注意,我以“转置形式”写入输出缓冲区,即位置(x,y)的每个线程在(y,x),(y + 1,x)处连续写入64个值。 ..(y + 63,x) 这是因为:

1)这是最快的方法!我写为(x,y)->(x + 1,y),...(x + 63,y)的版本肯定慢一些。

2)我需要这种格式,因为我正在使用ArrayFire库,该库随后需要加载缓冲区,但是它将以行优先的顺序消耗缓冲区,并将内容按列优先的顺序放入其数组中,不需要转置数组(它将使用大量的vram副本)

2 个答案:

答案 0 :(得分:1)

首先,正如您没有明确提及的那样,我将指出,如果可以的话,请使用GPU制造商的性能分析工具来验证瓶颈。即使似乎像瓶颈一样,也可能是红色鲱鱼。

但是,听起来好像全局内存写入可能是内核中的问题。由于您没有提供任何细节,因此我只能指出一些需要注意的一般事项:

1。内存布局

似乎设置中的每个工作项都连续地在内存中写入64个值。这意味着每个工作项都将写入不同的缓存行,这几乎肯定不是最佳选择。如果可以更改输出的内存布局,请尝试对其进行排列,以使工作项同时写入相邻内存位置。

例如,您当前可能有:

uint output_index = 64 * (get_global_size(0) * get_global_id(1) + get_global_id(0));
for (unsigned i = 0; i < 64; ++i)
{
    output[output_index + i] = calculation(inputs, i);
}

在这里,工作项目(0,0)首先将写入项目0,然后写入项目1,然后写入项目2,而工作项目(1,0)首先将写入项目64,然后写入65,依此类推。< / p>

如果工作项目(0,0)同时写入工作索引(1,0),则通常更快,依此类推。因此,如果可以的话,请尝试对输出数组进行布局,以使值维具有更高的跨度,从而可以编写:

uint stride = get_global_size(0) * get_global_size(1);
uint output_index = (get_global_size(0) * get_global_id(1) + get_global_id(0));
for (unsigned i = 0; i < 64; ++i)
{
    output[output_index] = calculation(inputs, i);
    output_index += stride;
}

2。使用本地内存作为中介

如果不能更改全局内存的布局,则可以将结果以对全局内存无效的顺序写入本地内存,然后将其有效地从本地内存复制到全局内存。所谓高效,是指您工作组中的相邻工作项应再次写入相邻的全局内存位置。您可以明确地执行此操作,也可以在内核中使用async_work_group_copy函数。

3。压缩

如果有某种方法可以更节省空间地表示您的64个值,则可以有很大帮助,特别是如果您随后将结果发送回主机CPU。例如,如果精度不是很重要并且范围受到限制,并且您当前正在使用float,则可以尝试使用half(16位)浮点值或short / ushort 16位整数值,虽然精度较高,但范围较小。另外,如果您的值以某种方式相关,则可以使用其他表示形式,例如共享指数。

4。在GPU上继续计算

如果您当前正在使用主机CPU上的计算结果,则可能会受到PCIe带宽的束缚,该带宽大大低于GPU到VRAM的带宽。在这种情况下,请考虑将您正在执行的任何进一步计算移至GPU上,即使此功能本身的CPU实现目前不是瓶颈。避免从VRAM复制到系统RAM可能会给您带来更大的推动力。

更好的是,如果您可以避免将结果完全写入全局内存中(例如,在同一个内核中执行向前计算,可能是在将中间结果存储在本地内存中以便与工作组共享之后),那么您可以完全避免内存瓶颈。

5。阅读OpenCL优化指南

您可能还会执行其他针对您的工作负载的优化。由于您尚未详细了解正在执行的操作,因此我们无法轻易地猜测出这些优化。 GPU制造商发布了OpenCL优化指南,请确保您已阅读并理解它们,并查看是否可以将任何建议应用于您的任务。

答案 1 :(得分:0)

根据我的经验,我还没有发现写入内核中的缓冲区的任何瓶颈,我相信这就是您所指的。

在每个线程中写入这64个值不应该成为您瓶颈所在的问题。在准备缓冲区参数时,可能在入队内核之前已经完成了一些工作。

相关问题