Бенчмарк A\b при помощи GPU Coder

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

Необходимые условия

  • Графический процессор NVIDIA с поддержкой CUDA ® с вычислительными возможностями 3,5 или выше.

  • Инструментарий и драйвер NVIDIA CUDA.

  • Переменные окружения для компиляторов и библиотек. Дополнительные сведения о поддерживаемых версиях компиляторов и библиотек см. в разделе Оборудование сторонних производителей. Для настройки переменных окружения смотрите Настройка обязательных продуктов.

Проверьте окружение GPU

Чтобы убедиться, что компиляторы и библиотеки, необходимые для выполнения этого примера, настроены правильно, используйте coder.checkGpuInstall функция.

envCfg = coder.gpuEnvConfig('host');
envCfg.BasicCodegen = 1;
envCfg.Quiet = 1;
coder.checkGpuInstall(envCfg);

Определите максимальный размер данных

Выберите соответствующий размер матрицы для расчетов путем определения объема системной памяти в ГБ, доступной для центрального процессора и графического процессора. Значение по умолчанию основано только на объеме памяти, доступном на графическом процессоре. Можно задать значение, соответствующее системе.

g = gpuDevice;
maxMemory = 0.25*g.AvailableMemory/1024^3;

Функция бенчмаркинга

Этот пример показывает матричное левое деление (\), включая стоимость передачи данных между центральным процессором и графическим процессором, чтобы получить четкое представление общего времени приложения при использовании GPU Coder™. Профилирование времени приложения не должно включать время для создания выборочных входных данных. The 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

Функция точки входа обратной косой черты

The backslash.m функция точки входа инкапсулирует операцию (\), для которой вы хотите сгенерировать код.

type backslash.m
function [x] = backslash(A,b)
%#codegen

%   Copyright 2017-2019 The MathWorks, Inc.

    coder.gpu.kernelfun();
    x = A\b;
end

Сгенерируйте код GPU

Создайте функцию для генерации функции 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

Создайте другую функцию для выполнения кода GPU, которая вызывает сгенерированную функцию GPU 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

Выполните бенчмарки

Когда вы выполняете бенчмарки, расчеты могут занять много времени. Печать некоторых промежуточных данных о состоянии по мере завершения бенчмаркинга для каждого размера матрицы. Инкапсулируйте цикл по всем размерам матрицы в функцию, чтобы сравнить одно-и двойную точность расчетов.

Фактические времена выполнения могут варьироваться для различных аппаратных строений. Этот бенчмаркинг был выполнен с помощью R2020a MATLAB на компьютере с 6-ядерным процессором 3.5GHz процессором Intel ® Xeon ® и графическим процессором NVIDIA TITAN Xp.

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;

Наконец, смотрите на скорость оператора обратной косой черты при сравнении графический процессор с центральный процессор.

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;

См. также

Функции

Объекты

Похожие темы