本文对比几个算法分别在CPU上计算,与GPU上计算的耗时。
测试环境:
CPU: Intel(R)_Core(TM)_i7-7700_CPU_@_3.60GHz x 8
GPU: NVIDIA GeForce GTX 1050
一,FFT计算的性能分析。
1,dft离散傅里叶变换的公式如下。
X为源数据,一个复数数组。Y = dft(X) 和 X = ifft(Y) 分别表示傅里叶变换和傅里叶逆变换。假设X的数据个数为n, 那么Y的数据个数也是n,计算公式如下:
其中,
2,对于1024个浮点复数,进行一维DFT变换。在保证计算结果正确一致的前提下,比较计算速度。
计算20次,将运行时间累加再计算平均值。得出如下统计结果:
其中,opencv是在CPU上进行多核并行计算的。cufftlib在GPU上计算,但是没有将数据拷贝耗时计算在内。可以看出,GPU上计算的花费时间,是CPU上的一半。
3,对于1024x1024的图像数据,进行2维DFT计算。在确保计算结果正确一致的前提下,比较计算速度(计算耗时)。
计算20次,将运行时间累加再计算平均值。得出如下统计结果:
可以看出,cufftlib 计算的耗时远远小于opencv的计算耗时,15us vs. 6ms,相差400倍,这个性能提升还是很可观的。
测试代码可以参考:https://gitee.com/yt2014/cuda-programs/tree/master/fft
4,这个里面有个问题,cufftPlan1d 和cufftPlan2d,这两个函数的运行都很耗时。对于1024x1024大小的计算来说,cufftPlan2d耗时可达168ms。这个操作,在fft的长度确定之后,只需要运行一次,所以对于特定的应用程序来说,可以在应用运行起来之时,执行一次,后面运行就很快了。
二,最大最小值计算的性能分析。
代码可以参考https://gitee.com/yt2014/cuda-programs/tree/master/max_min
对于以上3种方式,在保证结果正确一致的情况下,比较计算耗时,分别计算20次,然后计算各自的平均耗时。结果如下:
4,可以看出,
4.1在CPU上使用omp进行并行化,计算性能最好,花费629微秒;
4.2 其次是cuda GPU,花费717微秒;
4.3 花费时间最多的是CPU串行计算,花费2.11毫秒。
5,这个里面GPU跟omp相比,没有优势,因为计算最大最小值的时候,需要将每个数据与其它数据进行比较,或者是与相邻的数据比较,或者是与临时最大值最小值进行比较,更新最大最小值时还需要进行同步。使得这个计算,只能在小局部内并行,最后还是需要归约。而CPU上omp计算时,多个线程间对共享资源的更新,要快很多。
三,图像滤波计算性能分析
1.图像滤波是指,使用特性的滤波核,在图像矩阵中移动,对应的计算图像中与核矩阵重叠部分与滤波核矩阵的点乘,将结果保存到图像部分矩阵的中心点。滤波核一般是小的矩阵,对于sobel滤波来说,滤波核如下:
-1 -2 -1 -1 0 1
0 0 0 以及 -2 0 2
1 2 1 -1 0 1
分别对应y方向上的二阶导,和x方向上的一阶导
2.使用1024x1024的图像进行sobel滤波计算,
2.1 使用opencv在CPU上进行计算,
2.2 使用cuda GPU进行计算,
代码可以参考:https://gitee.com/yt2014/cuda-programs/tree/master/sobel
在保证计算结果正确一致的前提下,比较计算耗时。分别计算20次,然后计算平均值,计算结果如下:
2.3 如果不把GPU的数据拷贝时间考虑在内,运行结果如下:
2.4 如果计算拷贝时间,将结果数据从GPU拷贝回主机的时间 计算在内,那么运行结果如下:
3. 可以看出,如果不考虑GPU和主机之间的数据传输时间,GPU的计算性能还是很有优势,这是因为对于这个滤波计算,各个像素的计算可以实现完全的并行化,计算的结果之间没有依赖性。所以计算性能就有很大提升。
4.但是如果考虑数据传输时间,GPU计算耗时将比CPU计算耗时更长。所以在实际程序中,要考虑怎样避免数据传输(拷贝)时间影响程序的整体性能,主要的做法有,
4.1 让尽量多的计算在GPU上运行,让GPU的计算工作量远远大于拷贝传输数据的工作量,而且不要频繁的拷贝传输,这样就让拷贝传输耗时在整体耗时中占比减小,从而对整体性能影响不大。一个极端的情况就是,整个程序的计算都在GPU上进行,在开始的时候,将数据拷贝传输到GPU,然后所有计算都在GPU上进行,最后将结果拷贝回主机。
4.2 在CPU上启动多线程,一个线程在拷贝的过程中,另一个线程在执行计算,这样,计算与拷贝的时间就重合了,消除了一部分耗时。
4.3 使用cuda GPU中流(stream)的机制,对GPU的操作,使用异步方式,在一个流进行拷贝的时候,另一个流进行计算。