OpenCL或CUDA调用的开销

The overhead of a OpenCL or CUDA call?

本文关键字:开销 调用 CUDA OpenCL      更新时间:2023-10-16

我正在编写一个函数,它可以执行许多BLAS gemv操作。

我希望能够在GPU上做到这一点,我已经尝试过使用cuBlas。

我的问题是,我的矩阵和向量都很小,100x100矩阵和100向量。与CPU相比,CuBlas需要很长时间,我明白为什么了,CPU上的快速缓存和对GPU的调用开销很大。

因此,我正试图找出一种智能的方法来测量与GPU通信所需的时间。

这是CUDA设置调用并将其发送到图形处理器所需的时间,而不计算实际执行矩阵向量乘法所需时间。

我该怎么做?

更新:以下结果适用于2005硬件(nVidia 7800 GTX)上的手动FFT GPU算法,但显示了CPU-GPU传输瓶颈的原理

开销本身不是调用,而是GPU程序的编译以及GPU和主机之间的数据传输。CPU针对可以完全在缓存中执行的功能进行了高度优化,DDR3内存的延迟远低于为GPU提供服务的PCI Express总线。在编写GPU FFT例程(CUDA之前)时,我自己也经历过这种情况。请参阅此相关问题。

N FFTw(ms)GPUFFT(ms)8 0 0.06 3.352705 0.00688116 0.001 0.065 7.882117 0.01021732 0.001 0.075 17.10887 0.01469564 0.002 0.085 36.080118 0.026744128 0.004 0.093 76.724324 0.040122256 0.007 0.107 153.739856 0.066754512 0.015 0.115 320.200892 0.1346141024 0.034 0.125 657.735381 0.2705122048 0.076 0.156 1155.151507 0.4843314096 0.173 0.215 1834.212989 0.8045588192 0.483 0.32 2664.042421 1.51001116384 1.363 0.605 3035.4551 2.25541132768 3.168 1.14 3450.455808 2.78004165536 8.694 2.464 3404.628083 3.528726131072 15.363 5.027 3545.850483 3.05604262144 33.223 12.513 3016.885246 2.655183524288 72.918 25.879 3079.443664 2.8176671048576 173.043 76.537 2192.056517 2.2609042097152 331.553 157.427 2238.01491 2.1060814194304 801.544 430.518 1715.573229 1.861814

上表显示了基于内核大小的GPU FFT实现与CPU实现的时序。对于较小的尺寸,与GPU之间的数据传输占主导地位。较小的内核可以在CPU上执行,有些实现/大小完全在缓存中。这使得CPU成为小型操作的最佳选择。

另一方面,如果您需要以最小的移动量对数据执行大批量的工作,那么GPU将轻而易举地击败CPU。

就测量你的例子中的效果而言,我建议做一个类似上面的实验。尝试计算出每个矩阵大小的FLOPS,并在不同大小的矩阵的CPU和GPU上运行测试。将GPU与CPU的大小、时间和FLOPS输出到CSV文件。对于任何评测,请确保运行数百次代码迭代,并对整个过程计时,然后将总时间除以迭代,以获得循环时间。如果您的算法允许,也可以尝试不同形状的矩阵(例如10x100而不是100x10)。

使用这些数据,您可以了解开销是多少。要找到确切的答案,请重复相同的实验,但不用任何操作(只需从输入复制到输出)即可替换在GPU上执行的内部着色器代码。

希望这有帮助,

通过在缓冲区传输事件上使用clGetEventProfileInfo,可以从设备获取事件排队、提交、启动和完成的时间(以纳秒为单位)。

更多信息,以及如何在此处设置:http://www.khronos.org/registry/cl/sdk/1.0/docs/man/xhtml/clGetEventProfilingInfo.html

我认为,对于100x100矩阵,你最好坚持使用cpu进行运算。除非你同时有很多个要相乘,否则gpu的好处将很难被注意到,因为(小)传输开销和通常低得多的时钟速度。确保调整内核以使用尽可能多的本地数据——在我的硬件上,每个工作组有32KB,这应该足够容纳两个100x100矩阵。内置的点积功能也应该非常方便。

去年在ADFS上有一次关于这一点的精彩演讲(见会议ID:2908)http://developer.amd.com/afds/pages/OLD/sessions.aspx他们详细讨论了优化内核,以及对最佳大小进行硬编码。

您的矩阵已经在GPU上了吗?如果没有,CUBLAS可能会为您传输它们(称为thunking),这是一个额外的开销。

此外,GPU在如此小的计算中并没有真正发挥作用,也就是说,它可能会比CPU慢,因为你必须将结果传输回来。如果可以的话,使用更大的矩阵。否则,您可能希望使用流(cudaStream_t)在GPU上启动多个并行计算。

如果您想在CUDA中测量内核的执行时间,您需要将其(或GPU上计算的任何其他内容)包含在事件中,如使用CUDA运行时API时所示:

cudaEvent_t start, stop;
cudaEventRecord(&start);
struct timeval cpuStart, cpuEnd;
gettimeofday(&cpuStart, 0); // get start time on CPU
// Do something with CUDA on the GPU, e.g. call kernels, transfer memory, ...
gettimeofday(&cpuEnd, 0); // get end time on CPU
double seconds = cpuEnd.tv_sec - cpuStart.tv_sec;
double microseconds = cpuEnd.tv_usec - cpuStart.tv_usec;
double cpuDuration = (seconds * 1.0e6 + microseconds) / 1.0e3; // in milliseconds
cudaEventRecord(&stop);
// Wait until the stop event occurred
cudaError_t eventResult;
do
{
  eventResult = cudaEventQuery(stop);
}
while (eventResult == cudaErrorNotReady);
// Assert there was no error; check the CUDA Toolkit Reference for further info
assert(cudaSuccess == eventResult); // requires #include <assert.h> or <cassert>
// Retrieve the time
float gpuDuration = 0.0; // in milliseconds
cudaEventElapsedTime(&gpuDuration, start, stop);
// Release the event objects
cudaEventDestroy(stop);
cudaEventDestroy(start);

您可能需要检查对CUDA的每次调用的错误代码(至少使用断言),因为您可能会从以前的调用中得到错误,从而导致数小时的调试。。。

(注意:我主要使用CUDA驱动程序API,所以这可能无法开箱即用。很抱歉。)

EDIT:刚才看到您想要测量调用本身,而不是内核的持续时间。您可以通过简单地测量调用的CPU时间来实现这一点——请参阅上面更新的代码。这只适用于Linux,因为gettimeofday不适用于Windows(AFAIK)。

要查找调用开销,请调用一个尽可能少的CUDA内核。

for (int i=0; i<NLoops; i++) {
    gettimeofday(&cpuStart, 0); // get start time on CPU  
    // Call minimal CUDA kernel  
    gettimeofday(&cpuEnd, 0); // get end time on CPU 
    // save elapsed time
}

按照上面Alex p.的代码进行操作。

在内核中进行的处理越少,时间差就越大,这只是调用开销。

做一些实验,为NLoops找到一个好的值(可能是1000000)。请确保经过的时间比计时器的间隔长,否则您将以全零结束。如果发生这种情况,请编写一些内核代码,在您可以预测的固定时间间隔内执行:(n个循环,每个循环x个循环)。

很难删除cpuStart和cpuSend之间可能发生的所有非CUDA计算(如中断处理),但进行几次运行和平均可以获得良好的结果。