В этом примере показано, как выполнить сравнительный анализ решения линейной системы путем генерации кода графического процессора. Использовать матричное левое деление, также известное как mldivide или оператор обратной косой черты (\) для решения системы линейных уравнений A*x = b для x (то есть вычислить x = A\b).
Графический процессор NVIDIA ® с поддержкой CUDA ® с возможностью вычислений 3.5 или выше.
Набор инструментов и драйвер NVIDIA CUDA.
Переменные среды для компиляторов и библиотек. Сведения о поддерживаемых версиях компиляторов и библиотек см. в разделе Аппаратное обеспечение сторонних производителей. Сведения о настройке переменных среды см. в разделе Настройка необходимых продуктов.
Чтобы убедиться, что компиляторы и библиотеки, необходимые для выполнения этого примера, настроены правильно, используйте coder.checkGpuInstall функция.
envCfg = coder.gpuEnvConfig('host');
envCfg.BasicCodegen = 1;
envCfg.Quiet = 1;
coder.checkGpuInstall(envCfg);
Выберите соответствующий размер матрицы для вычислений, указав объем системной памяти в ГБ, доступный для ЦП и графического процессора. Значение по умолчанию основано только на объеме памяти, доступной графическому процессору. Можно указать значение, соответствующее системе.
g = gpuDevice; maxMemory = 0.25*g.AvailableMemory/1024^3;
В этом примере сравнивается матрица левого деления (\), включая стоимость передачи данных между CPU и GPU, чтобы получить четкое представление об общем времени применения при использовании GPU Coder™. Профилирование времени приложения не должно включать время на создание образцов входных данных. genData.m функция отделяет генерацию тестовых данных от функции точки входа, которая решает линейную систему.
type getData.m
function [A, b] = getData(n, clz)
% Copyright 2017-2019 The MathWorks, Inc.
fprintf('Creating a matrix of size %d-by-%d.\n', n, n);
A = rand(n, n, clz) + 100*eye(n, n, clz);
b = rand(n, 1, clz);
end
backslash.m функция точки входа инкапсулирует операцию (\), для которой требуется создать код.
type backslash.m
function [x] = backslash(A,b)
%#codegen
% Copyright 2017-2019 The MathWorks, Inc.
coder.gpu.kernelfun();
x = A\b;
end
Создайте функцию для генерации функции MEX графического процессора на основе определенного размера входных данных.
type genGpuCode.m
function [] = genGpuCode(A, b)
% Copyright 2017-2019 The MathWorks, Inc.
cfg = coder.gpuConfig('mex');
evalc('codegen -config cfg -args {A,b} backslash');
end
Производительность параллельных алгоритмов, решающих линейную систему, сильно зависит от размера матрицы. В этом примере сравнивается производительность алгоритма для различных размеров матрицы.
% Declare the matrix sizes to be a multiple of 1024. sizeLimit = inf; if ispc sizeLimit = double(intmax('int32')); end maxSizeSingle = min(floor(sqrt(maxMemory*1024^3/4)),floor(sqrt(sizeLimit/4))); maxSizeDouble = min(floor(sqrt(maxMemory*1024^3/8)),floor(sqrt(sizeLimit/8))); step = 1024; if maxSizeDouble/step >= 10 step = step*floor(maxSizeDouble/(5*step)); end sizeSingle = 1024:step:maxSizeSingle; sizeDouble = 1024:step:maxSizeDouble; numReps = 5;
Используйте общее прошедшее время в качестве показателя производительности, поскольку это позволяет сравнивать производительность алгоритма для различных размеров матрицы. Учитывая размер матрицы, функция бенчмаркинга создает матрицу A и правая сторона b один раз, а затем решает A\b несколько раз, чтобы получить точную меру времени, которое требуется.
type benchFcnMat.m
function time = benchFcnMat(A, b, reps)
% Copyright 2017-2019 The MathWorks, Inc.
time = inf;
% Solve the linear system a few times and take the best run
for itr = 1:reps
tic;
matX = backslash(A, b);
tcurr = toc;
time = min(tcurr, time);
end
end
Создайте другую функцию для выполнения кода графического процессора, которая вызывает сгенерированную функцию MEX графического процессора.
type benchFcnGpu.m
function time = benchFcnGpu(A, b, reps)
% Copyright 2017-2019 The MathWorks, Inc.
time = inf;
gpuX = backslash_mex(A, b);
for itr = 1:reps
tic;
gpuX = backslash_mex(A, b);
tcurr = toc;
time = min(tcurr, time);
end
end
При выполнении тестов вычисления могут занять много времени. Распечатайте промежуточную информацию о состоянии по завершении тестирования для каждого размера матрицы. Инкапсулируйте цикл по всем размерам матрицы в функцию для сравнения вычислений с одинарной и двойной точностью.
Фактическое время выполнения может различаться в различных конфигурациях оборудования. Этот сопоставительный анализ был сделан при помощи MATLAB R2020a на машине с 6 ядрами, Intel® Xeon® CPU на 3.5 ГГц и ТИТАНОМ NVIDIA Xp GPU.
type executeBenchmarks.m
function [timeCPU, timeGPU] = executeBenchmarks(clz, sizes, reps)
% Copyright 2017-2019 The MathWorks, Inc.
fprintf(['Starting benchmarks with %d different %s-precision ' ...
'matrices of sizes\nranging from %d-by-%d to %d-by-%d.\n'], ...
length(sizes), clz, sizes(1), sizes(1), sizes(end), ...
sizes(end));
timeGPU = zeros(size(sizes));
timeCPU = zeros(size(sizes));
for i = 1:length(sizes)
n = sizes(i);
fprintf('Size : %d\n', n);
[A, b] = getData(n, clz);
genGpuCode(A, b);
timeCPU(i) = benchFcnMat(A, b, reps);
fprintf('Time on CPU: %f sec\n', timeCPU(i));
timeGPU(i) = benchFcnGpu(A, b, reps);
fprintf('Time on GPU: %f sec\n', timeGPU(i));
fprintf('\n');
end
end
Выполните тесты с одинарной и двойной точностью.
[cpu, gpu] = executeBenchmarks('single', sizeSingle, numReps); results.sizeSingle = sizeSingle; results.timeSingleCPU = cpu; results.timeSingleGPU = gpu; [cpu, gpu] = executeBenchmarks('double', sizeDouble, numReps); results.sizeDouble = sizeDouble; results.timeDoubleCPU = cpu; results.timeDoubleGPU = gpu;
Starting benchmarks with 9 different single-precision matrices of sizes ranging from 1024-by-1024 to 25600-by-25600. Size : 1024 Creating a matrix of size 1024-by-1024. Time on CPU: 0.010894 sec Time on GPU: 0.012735 sec Size : 4096 Creating a matrix of size 4096-by-4096. Time on CPU: 0.256912 sec Time on GPU: 0.056594 sec Size : 7168 Creating a matrix of size 7168-by-7168. Time on CPU: 0.859825 sec Time on GPU: 0.138257 sec Size : 10240 Creating a matrix of size 10240-by-10240. Time on CPU: 2.190538 sec Time on GPU: 0.276720 sec Size : 13312 Creating a matrix of size 13312-by-13312. Time on CPU: 4.265689 sec Time on GPU: 0.481420 sec Size : 16384 Creating a matrix of size 16384-by-16384. Time on CPU: 8.167554 sec Time on GPU: 0.771961 sec Size : 19456 Creating a matrix of size 19456-by-19456. Time on CPU: 12.814798 sec Time on GPU: 1.150009 sec Size : 22528 Creating a matrix of size 22528-by-22528. Time on CPU: 19.060859 sec Time on GPU: 1.671082 sec Size : 25600 Creating a matrix of size 25600-by-25600. Time on CPU: 27.237500 sec Time on GPU: 2.318981 sec Starting benchmarks with 6 different double-precision matrices of sizes ranging from 1024-by-1024 to 16384-by-16384. Size : 1024 Creating a matrix of size 1024-by-1024. Time on CPU: 0.025851 sec Time on GPU: 0.021405 sec Size : 4096 Creating a matrix of size 4096-by-4096. Time on CPU: 0.435886 sec Time on GPU: 0.175009 sec Size : 7168 Creating a matrix of size 7168-by-7168. Time on CPU: 1.734958 sec Time on GPU: 0.765464 sec Size : 10240 Creating a matrix of size 10240-by-10240. Time on CPU: 4.240617 sec Time on GPU: 2.081403 sec Size : 13312 Creating a matrix of size 13312-by-13312. Time on CPU: 8.611123 sec Time on GPU: 4.415243 sec Size : 16384 Creating a matrix of size 16384-by-16384. Time on CPU: 19.387437 sec Time on GPU: 8.050974 sec
Постройте график результатов и сравните производительность процессора и графического процессора для одной и двойной точности.
Во-первых, посмотрите на производительность оператора обратной косой черты с одной точностью.
fig = figure; ax = axes('parent', fig); plot(ax, results.sizeSingle, results.timeSingleGPU, '-x', ... results.sizeSingle, results.timeSingleCPU, '-o') grid on; legend('GPU', 'CPU', 'Location', 'NorthWest'); title(ax, 'Single-Precision Performance') ylabel(ax, 'Time (s)'); xlabel(ax, 'Matrix Size'); drawnow;

Теперь посмотрите на производительность оператора обратной косой черты с двойной точностью.
fig = figure; ax = axes('parent', fig); plot(ax, results.sizeDouble, results.timeDoubleGPU, '-x', ... results.sizeDouble, results.timeDoubleCPU, '-o') legend('GPU', 'CPU', 'Location', 'NorthWest'); grid on; title(ax, 'Double-Precision Performance') ylabel(ax, 'Time (s)'); xlabel(ax, 'Matrix Size'); drawnow;

Наконец, посмотрите на ускорение оператора обратной косой черты при сравнении графического процессора с CPU.
speedupDouble = results.timeDoubleCPU./results.timeDoubleGPU; speedupSingle = results.timeSingleCPU./results.timeSingleGPU; fig = figure; ax = axes('parent', fig); plot(ax, results.sizeSingle, speedupSingle, '-v', ... results.sizeDouble, speedupDouble, '-*') grid on; legend('Single-precision', 'Double-precision', 'Location', 'SouthEast'); title(ax, 'Speedup of Computations on GPU Compared to CPU'); ylabel(ax, 'Speedup'); xlabel(ax, 'Matrix Size'); drawnow;

codegen | coder.checkGpuInstall | coder.gpu.constantMemory | coder.gpu.kernel | coder.gpu.kernelfun | gpucoder.matrixMatrixKernel | gpucoder.stencilKernel