带有 clSetKernelArg 的 OpenCL 竞争条件

OpenCL race condition with clSetKernelArg

来自 Khronos 网站关于 clSetKernelArg 的线程安全:

All OpenCL API calls are thread-safe except clSetKernelArg, which is safe to call from any host thread, and is safe to call re-entrantly so long as concurrent calls operate on different cl_kernel objects. However, the behavior of the cl_kernel object is undefined if clSetKernelArg is called from multiple host threads on the same cl_kernel object at the same time.

我的问题是,有没有一种方法可以定义这种行为,使内核可以从多个线程读取和写入单个内核对象?

我认为 std::atomic 在被内核修改的对象上可以防止这种未定义的行为,但根据我的尝试,它会导致内核的输出产生错误的值。有没有更好的方法来实现这个/处理案例的已知技术?

在分配对象的大小太大以至于为每个内核执行重新创建一个新对象会消耗太多内存的情况下,它可能很有用,并且首选 shared/overridable 对象。

where kernels can read and write from a single kernel object from multiple threads?

“内核”是指在 GPU 上执行的代码片段,“单个内核对象”是指主机代码中的 cl_kernel? GPU 上的内核永远看不到主机端存在的 cl_kernel 结构。我假设您正在谈论内核使用缓冲区对象 (cl_mem) 参数。

你可以把cl_kernel想成:

struct {
  size_t num_args;
  void* args[];
} _cl_kernel;
typedef struct _cl_kernel * cl_kernel;

如果您调用 clSetKernelArg(),它只是在该结构中设置一些内容。如果调用 clEnqueueNDRangeKernel(),它会拍摄 cl_kernel 结构(参数)的快照,并将其附加到某个内部设备队列。我所说的“快照”并不是说它会创建实际 cl_mem 缓冲区内容的隐藏快照;它只是复制对 cl_mem 参数的引用。因为它是一个引用,所以无论您是使用来自多个线程的单个 cl_kernel 对象,还是使用相同的名称多次调用 clCreateKernel,然后使用它们都没有关系每个线程中的cl_kernel;只是为了方便,最终结果是一样的。

如果您只有一个 in-order 命令队列,您的内核将按入队顺序确定地执行。如果您有多个命令队列(in- 或 out-of-order,无关紧要),则队列之间 没有任何 隐式 排序 [=47] =],所以如果你将同一个内核放入所有队列,它们将以随机顺序执行。您可以使用事件强制执行 explicit 订单。 IOW,你这样做:

cl_event event1, event2;
cl_kernel K;
...
clEnqueueNDRangeKernel(queue_1, K, ... , &event1);
clEnqueueNDRangeKernel(queue_2, K, ... , 1, &event1, &event2);

等这将强制内核执行等待前一个,即使它们在不同的队列中。但是你一次只能有一个内核使用缓冲区。

如果您希望多个 运行 内核同时使用同一个缓冲区,则取决于该缓冲区的使用模式。如果您只进行读取,则可以同时安全地使用来自任意数量内核的缓冲区。对于写入用途,如果您知道您只会写入缓冲区的一部分,则可以尝试使用 sub-buffers (clCreateSubBuffer)。否则你可能运气不好(也许你可以尝试原子操作,但它可能会使算法慢得无法使用)。