Этот пример смотрит на то, как мы можем протестировать решения в сравнении с эталоном линейной системы путем генерации кода графического процессора. Код 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.008953 sec Time on GPU: 0.012897 sec Size : 4096 Creating a matrix of size 4096-by-4096. Time on CPU: 0.241337 sec Time on GPU: 0.056992 sec Size : 7168 Creating a matrix of size 7168-by-7168. Time on CPU: 0.979883 sec Time on GPU: 0.126366 sec Size : 10240 Creating a matrix of size 10240-by-10240. Time on CPU: 2.456934 sec Time on GPU: 0.251279 sec Size : 13312 Creating a matrix of size 13312-by-13312. Time on CPU: 4.969921 sec Time on GPU: 0.440791 sec Size : 16384 Creating a matrix of size 16384-by-16384. Time on CPU: 9.099973 sec Time on GPU: 0.699288 sec Size : 19456 Creating a matrix of size 19456-by-19456. Time on CPU: 15.161504 sec Time on GPU: 1.043330 sec Size : 22528 Creating a matrix of size 22528-by-22528. Time on CPU: 20.989874 sec Time on GPU: 1.494572 sec Size : 25600 Creating a matrix of size 25600-by-25600. Time on CPU: 23.794767 sec Time on GPU: 2.062284 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.013808 sec Time on GPU: 0.019805 sec Size : 4096 Creating a matrix of size 4096-by-4096. Time on CPU: 0.459535 sec Time on GPU: 0.169425 sec Size : 7168 Creating a matrix of size 7168-by-7168. Time on CPU: 1.899516 sec Time on GPU: 0.726797 sec Size : 10240 Creating a matrix of size 10240-by-10240. Time on CPU: 4.896854 sec Time on GPU: 1.970779 sec Size : 13312 Creating a matrix of size 13312-by-13312. Time on CPU: 10.263277 sec Time on GPU: 4.184999 sec Size : 16384 Creating a matrix of size 16384-by-16384. Time on CPU: 18.258044 sec Time on GPU: 7.642277 sec Size : 19456 Creating a matrix of size 19456-by-19456. Time on CPU: 21.981840 sec Time on GPU: 12.661797 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