opencl中速度慢的问题

时间:2019-05-15 07:59:57

标签: c gpu opencl

我正在尝试第一次使用opencl,目标是计算数组中每一行的argmin。由于每一行的操作都独立于其他行,因此我认为将其轻松放在图形卡上是很容易的。

与仅使用外部forloop在cpu上运行代码时相比,使用此代码似乎可以获得更差的性能。

这是代码:

#pragma OPENCL EXTENSION cl_khr_fp64 : enable

int argmin(global double *array, int end)
{
  double minimum = array[0];
  int index;
  for (int j = 0; j < end; j++)
  {
    if (array[j] < minimum)
    {
      minimum = array[j];
      index = j;
    }
  }
  return index;
}

kernel void execute(global double *dist, global long *res, global double *min_dist)
{
  int row_size = 0;
  int i = get_global_id(0);

  int row_index = i * row_size;
  res[i] = argmin(&dist[row_index], row_size);
  min_dist[i] = dist[res[i] + row_index];

}

1 个答案:

答案 0 :(得分:0)

评论者提出了一些有效的观点,但我会尝试使其更具建设性和组织性:

  1. 您的数据似乎由double个精度浮点值组成。根据您的GPU,这本身可能是个坏消息。消费级GPU通常并未针对使用double进行优化,与单精度float操作相比,通常仅达到1/32或1/16的吞吐量。不过,许多专业级GPU(Quadro,Tesla,FirePro和某些Radeon Pro卡)都可以使用,与float相比,可实现1/2或1/4的吞吐量。由于您仅执行简单的算术运算(比较),并且很有可能您的运行时由内存访问控制,因此在消费类硬件上也可以。
  2. 我假设您的row_size实际上不是0,这将有助于知道真实(典型)值是什么,以及它是固定的,逐行还是每次运行都可变,但每一行都相同。无论如何,除非row_size非常小,否则您正在其上运行串行for的事实可能会使您的代码受阻。
  3. 您的工作量有多大?换句话说,数组中有多少行(如果变化,则给出一个典型范围)?如果它很小,您将几乎看不到GPU并行性的好处:GPU具有大量处理器,并且每个处理器可以调度几个线程。因此,您的工作项需要数以百计甚至更好的数以达到良好的硬件利用率。
  4. 您正在从(大概)系统内存中读取非常大的阵列,并且不对其执行任何密集操作。这意味着您的瓶颈通常在内存访问方面-对于分立的GPU,系统内存访问需要通过PCIe进行,因此该链接的速度将对您的性能造成限制。此外,您的内存访问模式远非GPU的理想选择-您通常希望工作项同时读取相邻的内存单元,因为内存单元通常一次获取64个字节或更多。

改进建议:

  • 分析。如果有可能,请使用GPU供应商的配置工具确定您的真正瓶颈。否则,我们只是在猜测。
  • 对于(4)-如果有可能,请尽量不要将大量数据移动太多。如果您可以在GPU上生成输入数组,请这样做,这样它们就永远不会离开VRAM。
  • 对于(4)-优化您的内存访问。 AMD,NVidia和Intel都具有OpenCL GPU优化指南,这些指南说明了如何执行此操作。本质上,重新组织数据布局或内核,以便相邻的工作项读取相邻的内存。理想情况下,您希望工作项0读取数组项0,工作项1读取数组项1,依此类推。您可能需要使用本地内存在工作项之间进行协调。另一个选择是读取每个工作项的矢量大小的数据块。 (例如,每个工作项一次读取一个double8),但在这种情况下请注意对齐情况。
  • 对于(2)和(3)-除非row_size非常小(并且固定),否则请尝试将循环拆分为多个工作项,并使用局部内存(归约算法)和全局内存中的原子操作进行协调。
  • 对于(1):如果您已经优化了其他所有内容,并且分析表明您在消费类硬件上比较double太慢,请检查是否可以将数据生成为float而不损失准确性(这也将使您的内存带宽问题减少一半),或者检查是否可以通过其他方式做得更好,例如,将double视为long,并手动拆包并比较指数和尾数使用整数运算。