罗兰谈MATLAB的艺术

将想法转化为MATLAB

GPU性能测试

今天我欢迎客座博主回来本Tordoff谁以前在这里写过如何在GPU上生成分形.他将继续下面的GPU主题,看看如何衡量GPU的性能。

内容

GPU性能测试

无论你是在考虑给自己买一个新的强大的GPU,还是刚刚花了一大笔钱买了一个,你都可能会问自己它有多快。在本文中,我将描述并尝试测量GPU的一些关键性能特征。这将让你深入了解使用GPU而不是CPU的相对优点,以及GPU之间的不同之处。

有大量的基准测试可供选择,所以我把范围缩小到三个测试:

  • 我们可以多快地将数据发送到GPU或再次读取数据?
  • GPU内核读写数据的速度有多快?
  • GPU的计算速度有多快?

在测量完这些后,我可以将我的GPU与其他GPU进行比较。

如何衡量时间

在下面几节中,每个测试将重复多次,以考虑到PC上的其他活动和第一次调用的开销。我尽量减少结果,因为外部因素只会减慢执行速度。

为了得到准确的计时数字,我使用等待(gpu)以确保GPU在停止计时器之前已经完成工作。在普通代码中不应该这样做。为了获得最佳性能,你想让GPU继续工作,而CPU则在处理其他事情。MATLAB自动处理所需的任何同步。

我把代码放入一个函数中,这样变量就有了作用域。这在内存性能方面有很大的不同,因为MATLAB能够更好地重用数组。

函数gpu_benchmarking
gpu = gpu device ();流('我有%s个GPU。\n'gpu.Name)
我有一个特斯拉C2075 GPU。

测试主机/GPU带宽

第一个测试试图测量数据发送到GPU和从GPU读取的速度。由于GPU插入PCI总线,这在很大程度上取决于您的PCI总线有多好,以及有多少其他设备正在使用它。然而,度量中也包含了一些开销,特别是函数调用开销和数组分配时间。因为这些存在于任何“真实世界”的GPU使用中,所以包含这些是合理的。

在以下测试中,使用gpuArray函数,用于分配/返回主机内存收集.数组是使用uint8每个元素都是一个字节。

请注意,在此测试中使用的PCI express v2,每个通道的理论带宽为0.5GB/s。对于NVIDIA特斯拉卡使用的16通道插槽(PCIe2 x16),这提供了理论上的8GB/s。

尺寸=功率(2,12:26);重复次数= 10;sendTimes = inf(大小(大小));gatherTimes = inf(大小(大小));Ii =1: nummel(尺寸)data = randi([0 255],尺寸(Ii), 1,“uint8”);Rr =1:重复计时= tic();gdata = gpuArray(data);等待(gpu);sendTimes(ii) = min(sendTimes(ii), toc(定时器));定时器= tic();Data2 = gather(gdata);% #好< NASGU >gatherTimes(ii) = min(gatherTimes(ii), toc(timer));结束结束sendBandwidth = (size ./sendTimes)/1e9;[maxSendBandwidth, maxsenddidx] = max(sendBandwidth);流('峰值发送速度%g GB/s\n',maxSendBandwidth) gatherBandwidth = (size ./gatherTimes)/1e9;[maxGatherBandwidth,maxGatherIdx] = max(gatherBandwidth);流('峰值采集速度%g GB/s\n'马克斯(gatherBandwidth))
峰值发送速度为5.70217 GB/s,峰值收集速度为3.99077 GB/s

在图中,您可以看到在每种情况下达到峰值的位置(圈出)。在较小的尺寸下,PCI总线的带宽无关紧要,因为开销占主导地位。在较大尺寸的PCI总线是限制因素,曲线变平。由于PC和我使用的所有gpu都使用相同的PCI v2,因此比较不同的gpu没有什么好处。不过,PCI v3硬件已经开始出现了,所以这在未来可能会变得更有趣。

持有semilogx(大小、sendBandwidth“b -”,大小,带宽,' r . - ')举行maxSendBandwidth semilogx(大小(maxSendIdx),“bo - - - - - -”“MarkerSize”10);maxGatherBandwidth semilogx(大小(maxGatherIdx),“ro - - - - - -”“MarkerSize”10);网格标题(“数据传输带宽”)包含('数组大小(字节)') ylabel (传输速度(GB/s))传说(“发送”“收集”“位置”“西北”

测试内存密集型操作

您可能想要执行的许多操作对数组的每个元素只做了很少的计算,因此主要取决于从内存中获取数据或写回数据所花费的时间。像ONES, zero, NAN, TRUE这样的函数只写它们的输出,而像TRANSPOSE, TRIL/TRIU这样的函数既读又写,但不做计算。即使是简单的运算符,如PLUS, MINUS, MTIMES对每个元素的计算量也很少,以至于它们只受内存访问速度的限制。

我可以使用一个简单的PLUS操作来测量我的机器读写内存的速度。这包括读取每个双精度数(即输入的每个元素8个字节),添加一个,然后再次将其写入(即每个元素又8个字节)。

sizeOfDouble = 8;readWritesPerElement = 2;memoryTimesGPU = inf(大小(大小));ii=1: numElements = size (ii)/sizeOfDouble;data = gpuArray。0 (numElements 1“双”);Rr =1:重复计时= tic();Jj =1:100 data = data + 1;结束等待(gpu);memoryTimesGPU(ii) = min(memoryTimesGPU(ii), toc(timer)/100);结束结束memoryBandwidth = readWritesPerElement*(size ./memoryTimesGPU)/1e9;[maxBWGPU, maxBWIdxGPU] = max(内存带宽);流(GPU上的峰值读写速度为%g GB/s\n'maxBWGPU)
GPU最高读写速度为110.993 GB/s

为了知道这是否快,我将它与CPU上运行的相同代码进行了比较。但是请注意,CPU有几个级别的缓存和一些奇怪的特性,比如“先读后写”,这可能会使结果看起来有点奇怪。对于我的个人电脑来说,主存的理论带宽是32GB/s,所以任何超过这个带宽的都可能是由于高效的缓存。

memoryTimesHost = inf(大小(大小));ii=1: numElements = size (ii)/sizeOfDouble;rr=1:重复hostData = 0 (numElements,1);定时器= tic();jj=1:100 hostData = hostData + 1;结束memoryTimesHost(ii) = min(memoryTimesHost(ii), toc(timer)/100);结束结束memoryBandwidthHost = 2*(sizes./memoryTimesHost)/1e9;[maxBWHost, maxBWHostIdx] = max(内存带宽主机);流('主机上的峰值写速度为%g GB/s\n'maxBWHost)绘制CPU和GPU结果。持有semilogx(大小、memoryBandwidth“b -”...大小、memoryBandwidthHost“k -”)举行maxBWGPU semilogx(大小(maxBWIdxGPU),“bo - - - - - -”“MarkerSize”10);maxBWHost semilogx(大小(maxBWHostIdx),“ko - - - - - -”“MarkerSize”10);网格标题(“读/写带宽”)包含('数组大小(字节)') ylabel (“速度(GB / s)”)传说(“阅读+写作(GPU)”“阅读+写作(主机)”“位置”“西北”
主机写速度峰值为44.6868 GB/s

很明显,gpu读写内存的速度要比从主机获取数据快得多。因此,在编写代码时,必须尽量减少主机- gpu或gpu -主机传输的数量。您必须将数据传输到GPU,然后在GPU上尽可能多地使用它,只有在绝对需要时才将其带回主机。更好的是,如果可以的话,在GPU上创建数据。

测试计算密集型计算

对于计算占主导地位的操作,内存速度就不那么重要了。在这种情况下,您可能更感兴趣的是计算执行的速度有多快。计算性能的一个很好的测试是矩阵-矩阵乘法。对于两个NxN矩阵相乘,浮点计算的总数为

$ flops (n) = 2n ^3 - n ^2$

如上所述,我在主机PC和GPU上计算这个操作的时间,以查看它们的相对处理能力:

尺寸=功率(2,12:2:24);N =根号(大小);mmTimesHost = inf(大小(大小));mmTimesGPU = inf(大小(大小));ii=1:数字(大小)A = rand(N(ii), N(ii));B = rand(N(ii), N(ii));首先在主机上做Rr =1:重复计时= tic();C = a * b;% #好< NASGU >mmTimesHost(ii) = min(mmTimesHost(ii), toc(定时器));结束%现在在GPU上A = gpuArray(A);B = gpuArray(B);Rr =1:重复计时= tic();C = a * b;% #好< NASGU >等待(gpu);mmTimesGPU(ii) = min(mmTimesGPU(ii), toc(定时器));结束结束mmGFlopsHost = (2*N.;^3 - n ^2)./mmTimesHost/1e9;[maxGFlopsHost,maxGFlopsHostIdx] = max(mmGFlopsHost);mmGFlopsGPU = (2*N。^3 - n ^2)./mmTimesGPU/1e9;[maxGFlopsGPU,maxGFlopsGPUIdx] = max(mmGFlopsGPU);流('峰值计算速率:%1.1f GFLOPS(主机),%1.1f GFLOPS (GPU)\n'...maxGFlopsHost maxGFlopsGPU)
峰值计算速率:73.7 GFLOPS(主机),330.9 GFLOPS (GPU)

现在画出顶点的位置。

持有semilogx(大小、mmGFlopsGPU“b -”, mmGFlopsHost,“k -”)举行maxGFlopsGPU semilogx(大小(maxGFlopsGPUIdx),“bo - - - - - -”“MarkerSize”10);maxGFlopsHost semilogx(大小(maxGFlopsHostIdx),“ko - - - - - -”“MarkerSize”10);网格标题(矩阵乘法计算率)包含(矩阵大小(边长)) ylabel (计算速率(GFLOPS))传说(“图形”“主机”“位置”“西北”

比较gpu

在测量了内存带宽和计算性能之后,我现在可以将我的GPU与其他GPU进行比较。之前我在几个不同的gpu上运行这些测试,并将结果存储在一个数据文件中。

离线=负载(“gpuBenchmarkResults.mat”);名称= [“这GPU”“这主机”offline.names];ioData = [maxBWGPU maxBWHost offline.memoryBandwidth];calcData = [maxGFlopsGPU maxGFlopsHost offline.mmGFlops];次要情节(1、2、1)栏([ioData(,)、南(元素个数(ioData), 1))”,“分组”);集(gca),“Xlim”, [0.6 1.4],“XTick”, []);传奇(名字{}):标题(内存带宽的), ylabel (“GB /秒”)次要情节(1、2、2)栏([calcData(,)、南(元素个数(calcData), 1))”,“分组”);集(gca),“Xlim”, [0.6 1.4],“XTick”, []);标题(的计算速度), ylabel (“GFLOPS”)设置(gcf“位置”得到(gcf“位置”)+[0 0 300 0]);

结论

这些测试揭示了一些关于gpu表现的事情:

  • 从主机内存到GPU内存的传输相对较慢,在我的例子中<6GB/s。
  • 一个好的GPU读/写内存的速度比主机PC读/写内存的速度快得多。
  • 给定足够大的数据,gpu执行计算的速度比主机PC快得多,在我的例子中是四倍多。

值得注意的是,在每个测试中,您都需要相当大的阵列来完全饱和您的GPU,无论是受内存限制还是受计算限制。当您同时处理数百万个元素时,您可以从GPU中获得最大收益。

如果您对GPU性能的更详细的基准测试感兴趣,请查看GPUBenchMATLAB中央文件交换

如果你对这些测量有疑问,或者发现我做错了什么或者可以改进的地方,请给我留言在这里




发布与MATLAB®R2012b

|
  • 打印
  • 发送电子邮件

评论

如欲留言,请点击在这里登录您的MathWorks帐户或创建一个新帐户。