Этот пример смотрит на то, как мы можем протестировать решения в сравнении с эталоном линейной системы путем генерации кода графического процессора. Код 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