exponenta event banner

Сравнительный тест A\b с помощью кодера графического процессора

В этом примере показано, как выполнить сравнительный анализ решения линейной системы путем генерации кода графического процессора. Использовать матричное левое деление, также известное как mldivide или оператор обратной косой черты (\) для решения системы линейных уравнений A*x = b для x (то есть вычислить x = A\b).

Предпосылки

Проверка среды графического процессора

Чтобы убедиться, что компиляторы и библиотеки, необходимые для выполнения этого примера, настроены правильно, используйте 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;

См. также

Функции

Объекты

Связанные темы