【问题标题】:Setting argument for kernel extremely slow (OpenCL)为内核设置参数非常慢(OpenCL)
【发布时间】:2020-09-22 05:08:46
【问题描述】:

在我的 OpenCL Dijkstra 算法实现中,迄今为止最慢的部分是将一维简化图矩阵写入内核参数,即全局内存。

我的图是一个二维数组;对于 OpenCL,它会被简化为一维数组,如下所示:

for (int q = 0; q < numberOfVertices; q++)
{
    for (int t = 0; t < numberOfVertices; t++)
    {
        reducedGraph[q * numberOfVertices + t] = graph[q][t];
    }
}

放入缓冲区:

cl::Buffer graphBuffer = cl::Buffer(context, CL_MEM_READ_WRITE, numberOfVertices * numberOfVertices * sizeof(int));

设置参数需要很长时间。对于我的 5,760,000 个顶点的测试,将数据写入参数需要 3 秒以上,而算法本身需要不到 1 毫秒: p>

kernel_dijkstra.setArg(5, graphBuffer);

内核使用图作为全局参数:

void kernel min_distance(global int* dist, global bool* verticesSet, const int sizeOfChunks, global int* result, const int huge_int, global int* graph, const int numberOfVertices)

有什么办法可以加快速度吗?谢谢!

编辑:我的内核代码:

// Kernel source, calculates minimum distance in segment and relaxes graph.
std::string kernel_code =
       void kernel min_distance(global int* dist, global bool* verticesSet, const int sizeOfChunks, global int* result, const int huge_int, global int* graph, const int numberOfVertices) {
           for (int b = 0; b < numberOfVertices; b++) {
               int gid = get_global_id(0);
               int min = huge_int, min_index = -1;
               for (int v = gid * sizeOfChunks; v < sizeOfChunks * gid + sizeOfChunks; v++) {
                   if (verticesSet[v] == false && dist[v] < min && dist[v] != 0) {
                       min = dist[v];
                       min_index = v;
                    }
               }
               result[gid] = min_index;
               if (gid != 0) continue;
               min = huge_int;
               min_index = -1;
               int current_min;
               for (int a = 0; a < numberOfVertices; a++) {
                   current_min = dist[result[a]];
                   if (current_min < min && current_min != -1 && current_min != 0) { min = current_min; min_index = result[a]; }
               }
               verticesSet[min_index] = true;
     // relax graph with found global min.
               int a = 0;
               int min_dist = dist[min_index];
               int current_dist;
               int compare_dist;
               for (int i = min_index * numberOfVertices; i < min_index * numberOfVertices + numberOfVertices; i++) {
                   current_dist = dist[a];
                   compare_dist = graph[min_index * numberOfVertices + a];
                   if (current_dist > min_dist + compare_dist && !verticesSet[a] && compare_dist != 0) {
                       dist[a] = min_dist + compare_dist;
                   }
                   a++;
               }
           }
       };

我如何入队:

    numberOfComputeUnits = default_device.getInfo<CL_DEVICE_MAX_COMPUTE_UNITS>();
queue.enqueueNDRangeKernel(kernel_dijkstra, 0, cl::NDRange(numberOfVertices), numberOfComputeUnits);

【问题讨论】:

  • 除非我的计算是错误的,否则 500 万个顶点应该分配一个 ~100TB 的缓冲区。这似乎有些不对劲,但如果是真的,3 秒是一个非常快的复制时间。可能另一种图形表示会是一个好主意。

标签: c++ performance opencl


【解决方案1】:

这里的错误是您的内存分配太大:5.76M 顶点需要 133TB 缓冲区,因为缓冲区大小是顶点数的二次方。 C++ 编译器和 OpenCL 都不会将此报告为错误,甚至您的内核看起来也可以正常启动和运行,但实际上它不会计算任何内容,因为内存不足,您会得到随机和未定义的结果。

通常.setArg(...) 的时间不应超过几毫秒。此外,最好在开始时只执行一次初始化部分(包括缓冲区分配、.setArg(...) 等),然后重复运行内核或在缓冲区中交换数据而不重新分配。

【讨论】:

  • 其实我错了,就是顶点少了。但即便如此:我是否需要拆分图形数组并循环它,每次都再次调用内核?还是我缺少什么?
  • 如果图真的那么大,你必须把它分成几批,一个接一个地计算。然而,真正的问题应该是:有没有办法使图表更小?也许通过对 Dijkstra 的一些已知优化?也许较小的图表足以满足您想要完成的任务?
  • 图表可以有不同的大小。 2400 个顶点可以正常工作(24 是我设备上的最大计算单元),但 4800 不能。将其与顺序和 OpenMP 实现进行比较,它在 2400 时要慢得多,并且无法处理更大的图形,而顺序和 OpenMP 最多可以处理 15000 左右。我希望用我的更大数字获得更快的结果GPU,但它看起来不像现在。该图仅用于放松,因此我可以将其移至其自己的内核并仅传递相关顶点,但我不确定这是否值得。
  • 由于您的计算时间和数据都随着顶点数的平方而扩展,如果您必须在每次迭代中跨 PCIe 移动顶点数据,GPU 可能在这里没有用处。数据传输是这里的瓶颈。解决方案是通过在 GPU 上并行化整个事情来避免跨 PCIe 的内存复制(如果可能的话):在开始时将输入顶点数据复制到 GPU 内存一次,让 GPU 在其视频内存上工作大量迭代,最后复制结果返回给 CPU。
  • 感谢您的帮助。我了解您是该领域的研究人员,非常感谢您抽出宝贵时间。我已将内核代码添加到问题中,也许您可​​以看看并帮助确定时间槽?我的猜测是循环太大,我应该减少它(比如步幅为 4 而不是 1),对吗?再次感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多