Сравнительное тестирование A\b с GPU Coder

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

Предпосылки

  • CUDA®-enabled NVIDIA® графический процессор с вычисляет возможность 3.5 или выше.

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

  • Переменные окружения для компиляторов и библиотек. Для получения дополнительной информации смотрите Переменные окружения.

Создайте новую папку и скопируйте соответствующие файлы

Следующая строка кода создает папку в вашей текущей рабочей папке (pwd) и копирует все соответствующие файлы в эту папку. Если вы не хотите выполнять эту операцию или если вы не можете сгенерировать файлы в этой папке, изменить вашу текущую рабочую папку.

gpucoderdemo_setup('gpucoderdemo_backslash_bench');

Проверьте среду графического процессора

Используйте 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™, но не время создавать наши данные. Мы поэтому разделяем генерацию данных от функции, которая решает линейную систему, и сгенерирует код и измерит только ту операцию.

type getData.m
function [A, b] = getData(n, clz)

%   Copyright 2017 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

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

Функция обратной косой черты инкапсулирует (\) операция, для которой мы хотим сгенерировать код.

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

%   Copyright 2017 The MathWorks, Inc.

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

Генерация кода графического процессора

Мы создаем функцию, чтобы сгенерировать MEX-функцию графического процессора на основе конкретного размера входных данных.

type genGpuCode.m
function [] = genGpuCode(A, b)

%   Copyright 2017 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

% We need to create a different function for GPU code execution that
% invokes the generated GPU MEX function.
type benchFcnGpu.m
function time = benchFcnMat(A, b, reps)

%   Copyright 2017 The MathWorks, Inc.

    time = inf;
    % We 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

function time = benchFcnGpu(A, b, reps)

%   Copyright 2017 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 18a на машине с 8 ядрами, центральном процессоре Intel® Xeon® на 2.6 ГГц и Титане NVIDIA X графических процессоров.

type executeBenchmarks.m
function [timeCPU, timeGPU] = executeBenchmarks(clz, sizes, reps)

%   Copyright 2017 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.008503 sec
Time on GPU: 0.013165 sec

Size : 4096
Creating a matrix of size 4096-by-4096.
Time on CPU: 0.206177 sec
Time on GPU: 0.056092 sec

Size : 7168
Creating a matrix of size 7168-by-7168.
Time on CPU: 0.811148 sec
Time on GPU: 0.126642 sec

Size : 10240
Creating a matrix of size 10240-by-10240.
Time on CPU: 1.934786 sec
Time on GPU: 0.249333 sec

Size : 13312
Creating a matrix of size 13312-by-13312.
Time on CPU: 3.890363 sec
Time on GPU: 0.440438 sec

Size : 16384
Creating a matrix of size 16384-by-16384.
Time on CPU: 6.984814 sec
Time on GPU: 0.697650 sec

Size : 19456
Creating a matrix of size 19456-by-19456.
Time on CPU: 11.127106 sec
Time on GPU: 1.043267 sec

Size : 22528
Creating a matrix of size 22528-by-22528.
Time on CPU: 16.664898 sec
Time on GPU: 1.494048 sec

Size : 25600
Creating a matrix of size 25600-by-25600.
Time on CPU: 23.995115 sec
Time on GPU: 2.061206 sec

Starting benchmarks with 7 different double-precision matrices of sizes
ranging from 1024-by-1024 to 19456-by-19456.
Size : 1024
Creating a matrix of size 1024-by-1024.
Time on CPU: 0.018048 sec
Time on GPU: 0.020526 sec

Size : 4096
Creating a matrix of size 4096-by-4096.
Time on CPU: 0.392711 sec
Time on GPU: 0.167556 sec

Size : 7168
Creating a matrix of size 7168-by-7168.
Time on CPU: 1.507990 sec
Time on GPU: 0.730197 sec

Size : 10240
Creating a matrix of size 10240-by-10240.
Time on CPU: 3.829732 sec
Time on GPU: 1.976515 sec

Size : 13312
Creating a matrix of size 13312-by-13312.
Time on CPU: 7.972217 sec
Time on GPU: 4.201351 sec

Size : 16384
Creating a matrix of size 16384-by-16384.
Time on CPU: 13.769837 sec
Time on GPU: 7.681881 sec

Size : 19456
Creating a matrix of size 19456-by-19456.
Time on CPU: 22.239325 sec
Time on GPU: 12.679025 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;

Очистка

Удалите временные файлы и возвратитесь к исходной папке.

cleanup