我正在为我的项目使用 opencl 1.2 c++ 包装器。我想知道调用内核的正确方法是什么。在我的例子中,我有 2 个设备,数据应该同时发送给它们。
我将我的数据分成两个 block ,两个设备都应该能够分别对它们执行计算。它们没有互连,也不需要知道其他设备中发生了什么。
当数据被发送到两个设备时,我想在我的程序继续之前等待内核完成。因为我将使用从两个内核返回的结果。所以我不想在内核返回之前开始读取数据。
我有两种方法。在我的案例中,哪一个在编程上是正确的:
方法一:
for (int i = 0; i < numberOfDevices; i++) {
// Enqueue the kernel.
kernelGA(cl::EnqueueArgs(queue[iter],
arguments etc...);
queue[i].flush();
}
// Wait for the kernels to return.
for (int i = 0; i < numberOfDevices; i++) {
queue[i].finish();
}
方法二:
for (int i = 0; i < numberOfDevices; i++) {
// Enqueue the kernel.
kernelGA(cl::EnqueueArgs(queue[iter],
arguments etc...);
}
for (int i = 0; i < numberOfDevices; i++) {
queue[i].flush();
}
// Wait for the kernels to return.
for (int i = 0; i < numberOfDevices; i++) {
queue[i].finish();
}
或者它们都不正确并且有更好的方法来等待我的内核返回?
最佳答案
假设每个设备都在自己的内存中计算:
我会选择你的 method-1 的多线程 (for) 循环版本。因为 opencl 不强制供应商进行异步排队。例如,Nvidia 对某些驱动程序和硬件进行同步排队,而 amd 具有异步排队。
当每个设备由一个单独的线程驱动时,它们应该在同步读取部分结果之前将 Write+Compute 一起入队(第二个线程循环)
拥有多个线程也有利于自旋等待类型同步 (clfinish),因为多个自旋等待循环是并行工作的。这应该可以节省一毫秒的时间。
Flush 帮助 amd 等供应商尽早开始排队。
要使所有设备的输入和输出正确,只需要两个完成命令就足够了。一个在写入+计算之后,然后一个在读取(结果)之后。因此每个设备都获得相同的时间步数据并在相同的时间步产生结果。如果队列类型是有序的,则 Write 和 Compute 不需要在它们之间完成,因为它是一个一个地计算的。此外,这不需要阻塞读取操作。
琐碎的完成命令总是会降低性能。
注意:我已经使用所有这些编写了一个负载均衡器,并且当使用基于事件的同步而不是完成时,它的性能更好。 Finish 比基于事件的完成更容易,但同步时间更长。
此外,单队列并不总是将 GPU 推向极限。每个设备至少使用 4 个队列可确保在我的 amd 系统上隐藏写入和计算的延迟。有时甚至 16 个队列也能提供更多帮助。但对于 io 瓶颈情况可能需要更多。
例子:
thread1
Write
Compute
Synchronization with other thread
Thread2
Write
Compute
Synchronization with other thread
Thread 1
Read
Synchronization with other thread
Thread2
Read
Synchronization with other thread
琐碎的同步会降低性能,因为驱动程序不知道您的意图,他们会保持原样。因此,您应该消除不必要的完成命令,并尽可能将阻塞写入转换为非阻塞写入。
零同步也是错误的,因为 opencl 不会强制供应商在几次入队后开始计算。它可能会在几分钟甚至几秒钟内无限增长到千兆字节的内存。
关于c++ - 调用内核后使用 queue.flush() 和 queue.finish() 的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42096778/