В этом примере показано, как протестировать решение линейной системы путем генерации кода GPU. Используйте матричное левое деление, также известное как mldivide
или оператор обратной косой черты (\), чтобы решить систему линейных уравнений A*x = b
для x
(то есть вычислите x = A\b
).
Графический процессор NVIDIA с поддержкой CUDA ® с вычислительными возможностями 3,5 или выше.
Инструментарий и драйвер NVIDIA CUDA.
Переменные окружения для компиляторов и библиотек. Дополнительные сведения о поддерживаемых версиях компиляторов и библиотек см. в разделе Оборудование сторонних производителей. Для настройки переменных окружения смотрите Настройка обязательных продуктов.
Чтобы убедиться, что компиляторы и библиотеки, необходимые для выполнения этого примера, настроены правильно, используйте 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
Создайте функцию для генерации функции 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;
codegen
| coder.checkGpuInstall
| coder.gpu.constantMemory
| coder.gpu.kernel
| coder.gpu.kernelfun
| gpucoder.matrixMatrixKernel
| gpucoder.stencilKernel