Main Content

本页采用了机器翻译。点击此处可查看最新英文版本。

测量 GPU 性能

此示例展示了如何测量 GPU 的一些关键性能特征。

GPU 可用于加速某些类型的计算。然而,不同的 GPU 设备之间的 GPU 性能存在很大差异。为了量化 GPU 的性能,使用了三种测试:

  • 数据可以多快被发送到 GPU 或从 GPU 读回?

  • GPU 内核读写数据的速度有多快?

  • GPU 执行计算的速度有多快?

测量这些之后,可以将 GPU 的性能与主机 CPU 进行比较。这为 GPU 需要多少数据或计算才能比 CPU 更具优势提供了指导。

设置

gpu = gpuDevice();
fprintf('Using an %s GPU.\n', gpu.Name)
Using an NVIDIA RTX A5000 GPU.
sizeOfDouble = 8; % Each double-precision number needs 8 bytes of storage
sizes = power(2, 14:28);

测试主机/GPU 带宽

第一个测试估计数据发送到 GPU 和从 GPU 读取的速度。由于 GPU 插入 PCI 总线,这在很大程度上取决于 PCI 总线的速度以及有多少其他东西正在使用它。然而,测量中还包含一些开销,特别是函数调用开销和数组分配时间。由于这些存在于 GPU 的任何“现实世界”使用中,因此将它们包括在内是合理的。

在以下测试中,使用 gpuArray函数分配内存并将数据发送到 GPU。分配内存并使用 gather 将数据传输回主机内存。

请注意,此测试中使用的 GPU 支持 PCI Express® 版本 4.0,其每通道理论带宽为 1.97GB/s。对于 NVIDIA® 计算卡使用的 16 通道插槽,理论上可达到 31.52GB/s。

sendTimes = inf(size(sizes));
gatherTimes = inf(size(sizes));
for ii=1:numel(sizes)
    numElements = sizes(ii)/sizeOfDouble;
    hostData = randi([0 9], numElements, 1);
    gpuData = randi([0 9], numElements, 1, 'gpuArray');
    % Time sending to GPU
    sendFcn = @() gpuArray(hostData);
    sendTimes(ii) = gputimeit(sendFcn);
    % Time gathering back from GPU
    gatherFcn = @() gather(gpuData);
    gatherTimes(ii) = gputimeit(gatherFcn);
end
sendBandwidth = (sizes./sendTimes)/1e9;
[maxSendBandwidth,maxSendIdx] = max(sendBandwidth);
fprintf('Achieved peak send speed of %g GB/s\n',maxSendBandwidth)
Achieved peak send speed of 9.5407 GB/s
gatherBandwidth = (sizes./gatherTimes)/1e9;
[maxGatherBandwidth,maxGatherIdx] = max(gatherBandwidth);
fprintf('Achieved peak gather speed of %g GB/s\n',max(gatherBandwidth))
Achieved peak gather speed of 4.1956 GB/s

在下图中,每种情况的峰值都被圈出来了。由于数据集较小,因此开销占主导地位。当数据量较大时,PCI 总线就成为限制因素。

semilogx(sizes, sendBandwidth, 'b.-', sizes, gatherBandwidth, 'r.-')
hold on
semilogx(sizes(maxSendIdx), maxSendBandwidth, 'bo-', 'MarkerSize', 10);
semilogx(sizes(maxGatherIdx), maxGatherBandwidth, 'ro-', 'MarkerSize', 10);
grid on
title('Data Transfer Bandwidth')
xlabel('Array size (bytes)')
ylabel('Transfer speed (GB/s)')
legend('Send to GPU', 'Gather from GPU', 'Location', 'NorthWest')
hold off

Figure contains an axes object. The axes object with title Data Transfer Bandwidth contains 4 objects of type line. These objects represent Send to GPU, Gather from GPU.

测试内存密集型操作

许多操作对数组的每个元素进行的计算都很少,因此主要取决于从内存中获取数据或将其写回所花费的时间。oneszerosnantrue 等函数只写入其输出,而 transposetril 等函数既读取又写入,但不进行计算。即使是像 plusminusmtimes 这样的简单运算符,每个元素的计算也非常少,因此它们仅受内存访问速度的限制。

函数 plus 对每个浮点运算执行一次内存读取和一次内存写入。因此,它应该受到内存访问速度的限制,并提供读写操作速度的良好指示符。

memoryTimesGPU = inf(size(sizes));
for ii=1:numel(sizes)
    numElements = sizes(ii)/sizeOfDouble;
    gpuData = randi([0 9], numElements, 1, 'gpuArray');
    plusFcn = @() plus(gpuData, 1.0);
    memoryTimesGPU(ii) = gputimeit(plusFcn);
end
memoryBandwidthGPU = 2*(sizes./memoryTimesGPU)/1e9;
[maxBWGPU, maxBWIdxGPU] = max(memoryBandwidthGPU);
fprintf('Achieved peak read+write speed on the GPU: %g GB/s\n',maxBWGPU)
Achieved peak read+write speed on the GPU: 659.528 GB/s

现在将其与 CPU 上运行的相同代码进行比较。

memoryTimesHost = inf(size(sizes));
for ii=1:numel(sizes)
    numElements = sizes(ii)/sizeOfDouble;
    hostData = randi([0 9], numElements, 1);
    plusFcn = @() plus(hostData, 1.0);
    memoryTimesHost(ii) = timeit(plusFcn);
end
memoryBandwidthHost = 2*(sizes./memoryTimesHost)/1e9;
[maxBWHost, maxBWIdxHost] = max(memoryBandwidthHost);
fprintf('Achieved peak read+write speed on the host: %g GB/s\n',maxBWHost)
Achieved peak read+write speed on the host: 71.0434 GB/s
% Plot CPU and GPU results.
semilogx(sizes, memoryBandwidthGPU, 'b.-', ...
    sizes, memoryBandwidthHost, 'r.-')
hold on
semilogx(sizes(maxBWIdxGPU), maxBWGPU, 'bo-', 'MarkerSize', 10);
semilogx(sizes(maxBWIdxHost), maxBWHost, 'ro-', 'MarkerSize', 10);
grid on
title('Read+write Bandwidth')
xlabel('Array size (bytes)')
ylabel('Speed (GB/s)')
legend('GPU', 'Host', 'Location', 'NorthWest')
hold off

Figure contains an axes object. The axes object with title Read+write Bandwidth contains 4 objects of type line. These objects represent GPU, Host.

将此图与上面的数据传输图进行比较,可以清楚地看出,GPU 从其内存读取和写入的速度通常比从主机获取数据的速度快得多。因此,尽量减少主机-GPU 或 GPU-主机内存传输的次数非常重要。理想情况下,程序应该将数据传输到 GPU,然后在 GPU 上尽可能多地处理数据,并仅在完成后将其带回主机。更好的方法是首先在 GPU 上创建数据。

测试计算密集型操作

对于从内存读取或写入每个元素执行的浮点计算次数较多的操作,内存速度就没那么重要了。在这种情况下,浮点单元的数量和速度是限制因素。据称这些操作具有很高的“计算密度”。

矩阵与矩阵相乘是计算性能的一个很好的测试。对于两个 N×N 矩阵的乘法,浮点计算的总数为

FLOPS(N)=2N3-N2.

读取两个输入矩阵并写入一个结果矩阵,总共读取或写入 3N2 个元素。这给出了 (2N - 1)/3 FLOP/element 的计算密度。将其与上面使用的 plus 进行对比,其计算密度为 1/2 FLOP/element。

sizes = power(2, 12:2:24);
N = sqrt(sizes);
mmTimesHost = inf(size(sizes));
mmTimesGPU = inf(size(sizes));
for ii=1:numel(sizes)
    % First do it on the host
    A = rand( N(ii), N(ii) );
    B = rand( N(ii), N(ii) );
    mmTimesHost(ii) = timeit(@() A*B);
    % Now on the GPU
    A = gpuArray(A);
    B = gpuArray(B);
    mmTimesGPU(ii) = gputimeit(@() A*B);
end
mmGFlopsHost = (2*N.^3 - N.^2)./mmTimesHost/1e9;
[maxGFlopsHost,maxGFlopsHostIdx] = max(mmGFlopsHost);
mmGFlopsGPU = (2*N.^3 - N.^2)./mmTimesGPU/1e9;
[maxGFlopsGPU,maxGFlopsGPUIdx] = max(mmGFlopsGPU);
fprintf(['Achieved peak calculation rates of ', ...
    '%1.1f GFLOPS (host), %1.1f GFLOPS (GPU)\n'], ...
    maxGFlopsHost, maxGFlopsGPU)
Achieved peak calculation rates of 354.4 GFLOPS (host), 414.0 GFLOPS (GPU)

现在绘制它来查看峰值在哪里达到。

semilogx(sizes, mmGFlopsGPU, 'b.-', sizes, mmGFlopsHost, 'r.-')
hold on
semilogx(sizes(maxGFlopsGPUIdx), maxGFlopsGPU, 'bo-', 'MarkerSize', 10);
semilogx(sizes(maxGFlopsHostIdx), maxGFlopsHost, 'ro-', 'MarkerSize', 10);
grid on
title('Double precision matrix-matrix multiply')
xlabel('Matrix size (numel)')
ylabel('Calculation Rate (GFLOPS)')
legend('GPU', 'Host', 'Location', 'NorthWest')
hold off

Figure contains an axes object. The axes object with title Double precision matrix-matrix multiply contains 4 objects of type line. These objects represent GPU, Host.

结论

这些测试揭示了 GPU 性能的一些重要特征:

  • 从主机内存到 GPU 内存和返回的传输相对较慢。

  • 良好的 GPU 读取/写入其内存的速度比主机 CPU 读取/写入其内存的速度快得多。

  • 如果数据足够大,GPU 可以比主机 CPU 更快地执行计算。

值得注意的是,在每次测试中都需要相当大的数组才能完全饱和 GPU,无论是受到内存还是计算的限制。GPU 在同时处理数百万个元素时具有最大的优势。

更详细的 GPU 基准测试(包括不同 GPU 之间的比较)可在 MATLAB® 中央文件交换上的 GPUBench 中获取。

另请参阅

|

相关主题