OpenCL 'non-blocking' 读取的成本高于预期

OpenCL 'non-blocking' reads have higher cost than expected

考虑以下代码,它在 1 到 100000 之间排队 'non-blocking' 随机访问缓冲区读取并测量时间:

#define __CL_ENABLE_EXCEPTIONS

#include <CL/cl.hpp>
#include <vector>
#include <iostream>
#include <chrono>

#include <stdio.h>

static const int size = 100000;
int host_buf[size];

int main() {
    cl::Context ctx(CL_DEVICE_TYPE_DEFAULT, nullptr, nullptr, nullptr);
    std::vector<cl::Device> devices;
    ctx.getInfo(CL_CONTEXT_DEVICES, &devices);
    printf("Using OpenCL devices: \n");
    for (auto &dev : devices) {
        std::string dev_name = dev.getInfo<CL_DEVICE_NAME>();
        printf("        %s\n", dev_name.c_str());
    }

    cl::CommandQueue queue(ctx);

    cl::Buffer gpu_buf(ctx, CL_MEM_READ_WRITE, sizeof(int) * size, nullptr, nullptr);

    std::vector<int> values(size);

    // Warmup
    queue.enqueueReadBuffer(gpu_buf, false, 0, sizeof(int), &(host_buf[0]));
    queue.finish();

    // Run from 1 to 100000 sized chunks
    for (int k = 1; k <= size; k *= 10) {
        auto cstart = std::chrono::high_resolution_clock::now();
        for (int j = 0; j < k; j++)
            queue.enqueueReadBuffer(gpu_buf, false, sizeof(int) * (j * (size / k)), sizeof(int), &(host_buf[j]));
        queue.finish();
        auto cend = std::chrono::high_resolution_clock::now();
        double time = std::chrono::duration<double>(cend - cstart).count() * 1000000.0;
        printf("%8d: %8.02f us\n", k, time);
    }
    return 0;
}

一如既往,有一些随机变化,但对我来说典型的输出是这样的:

       1:    10.03 us
      10:   107.93 us
     100:   794.54 us
    1000:  8301.35 us
   10000: 83741.06 us
  100000: 981607.26 us

虽然我确实预计单次读取会有相对较高的延迟,但考虑到需要 PCIe 往返,我对将后续读取添加到队列的高成本感到惊讶 - 好像真的没有'queue',但每次读取都会增加完整的延迟惩罚。这是在 Linux 和驱动程序版本 455.45.01 的 GTX 960 上。

您正在使用单个顺序命令队列。因此,所有排队的读取都由硬件/驱动程序按顺序执行。

'non-blocking' 方面仅表示调用本身是异步的,并且在 GPU 工作时不会阻塞您的主机代码。 在您的代码中,您使用 clFinish 阻塞直到所有读取完成。

所以是的,这是预期的行为。您将为每次 DMA 传输支付全额罚金。

只要您创建有序命令队列(默认),其他 GPU 的行为相同。

如果您的硬件/驱动程序支持无序队列,您可以使用它们来潜在地重叠 DMA 传输。或者,您可以使用多个有序队列。但性能当然取决于硬件和驱动程序。

使用多队列/无序队列更高级一些。您应该确保正确利用事件以避免竞争条件或导致未定义的行为。

为了减少与 GPU 主机 DMA 传输相关的延迟,建议您使用固定主机缓冲区而不是 std::vector。固定主机缓冲区通常通过 clCreateBufferCL_MEM_ALLOC_HOST_PTR 标志创建。