我正在为我的项目使用opencl 1.2 c ++包装器。我想知道调用我的内核的正确方法是什么。在我的情况下,我有2个设备,数据应该同时发送给他们。
我将数据分成两个块,两个设备都应该能够分别对它们进行计算。它们没有互连,他们不需要知道其他设备发生了什么。
当数据发送到两个设备时,我想在程序进一步发展之前等待内核完成。因为我将使用从两个内核返回的结果。所以我不想在内核返回之前开始读取数据。
我有两种方法。在我的情况下,哪一个在编程上是正确的:
方法1:
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();
}
方法2:
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();
}
或者没有一个是正确的,有更好的方法等待我的内核返回?
答案 0 :(得分:1)
假设每个设备在自己的内存中计算:
我会选择方法-1的多线程(for)循环版本。因为opencl不会强迫供应商进行异步排队。例如,Nvidia对某些驱动程序和硬件进行同步排队,而amd具有异步入队功能。
当每个设备由一个单独的线程驱动时,它们应该在同步之前将Write + Compute排队以读取部分结果(第二个线程循环)
多个线程也有利于自旋等待类型同步(clfinish),因为多个自旋等待循环并行工作。这应该节省一毫秒的时间。
Flush帮助像amd这样的供应商开始将Early排队。
要为所有设备提供正确的输入和正确的输出,只需要两个完成命令即可。一次写入+计算然后一次读取(结果)。因此,每个设备获得相同的时间步长数据并在同一时间步骤产生结果。如果队列类型是有序的,则Write和Compute不需要在它们之间完成,因为它逐个计算。此外,这不需要读取操作来阻止。
琐碎的完成命令总是会导致性能下降。
注意:我已经使用所有这些编写了一个负载均衡器,并且它在使用基于事件的同步而不是完成时表现更好。完成更容易,但同步时间比基于事件的更快。
此外,单个队列并不总是将gpu推向极限。每个设备使用至少4个队列可确保在我的amd系统上隐藏写入和计算的延迟。有时甚至16个队列可以帮助更多。但对于瓶颈情况,可能还需要更多。
示例:
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并没有强制供应商开始计算几次排队。它可以在几分钟甚至几秒内无限期地增长到几千兆字节的内存。
答案 1 :(得分:0)
您应该使用方法1. clFlush是保证向设备发出命令的唯一方法(而不是在发送之前在某处缓冲)。