Ответ 1
Я связался с технической поддержкой Mathworks, и Rylan наконец прояснил эту проблему. (Спасибо, Райлан!) Его полный ответ ниже. Проблема с функцией vs script связана с некоторыми оптимизациями. Matlab автоматически применяется к функциям (но не к скриптам), которые не работают должным образом.
Ответ Rylan:
Благодарим вас за терпение по этому вопросу. Я проконсультировался с разработчиками вычислительных машин MATLAB, чтобы понять это лучше.
Эта проблема вызвана внутренними оптимизациями, выполняемыми MATLAB, когда сталкиваются с некоторыми конкретными операциями, такими как умножение матрицы матриц и транспонирование. Некоторые из этих оптимизаций могут быть активированы специально при выполнении функции MATLAB (или анонимной функции), а не в скрипте.
Когда ваш исходный код выполнялся из сценария, определенная оптимизация транспонирования матрицы не выполняется, что приводит к тому, что выражение "res2" выполняется быстрее, чем выражение "res1":
n = 2000;
a=gpuArray(sprand(n,n,0.01));
b=gpuArray(rand(n));
tic;res1=a*b*a;wait(gpuDevice);toc % Elapsed time is 0.884099 seconds.
tic;res2=transpose(transpose(a)*transpose(a*b));wait(gpuDevice);toc % Elapsed time is 0.068855 seconds.
Однако, когда вышеуказанный код помещается в файл функции MATLAB, выполняется дополнительная оптимизация времени транспозиции матрицы, которая заставляет выражение "res2" проходить через другой путь кода (и другой вызов функции библиотеки CUDA) по сравнению с той же строкой, что и вызванный из сценария. Поэтому эта оптимизация генерирует более медленные результаты для строки "res2" при вызове из файла функций.
Чтобы избежать возникновения этой проблемы в файле функций, операции переноса и умножения необходимо разбить так, чтобы MATLAB не применял эту оптимизацию. Разделение каждого предложения в выражении "res2" представляется достаточным для этого:
tic;i1=transpose(a);i2=transpose(a*b);res3=transpose(i1*i2);wait(gpuDevice);toc % Elapsed time is 0.066446 seconds.
В приведенной выше строке "res3" генерируется из двух промежуточных матриц: "i1" и "i2". Производительность (в моей системе), по-видимому, соответствует показателю выражения "res2" при выполнении из сценария; кроме того, выражение 'res3' также показывает аналогичную производительность при исполнении из файла функции MATLAB. Однако обратите внимание, что для хранения транспонированной копии исходного массива может использоваться дополнительная память. Пожалуйста, дайте мне знать, если вы видите разные характеристики производительности в своей системе, и я могу исследовать это дальше.
Кроме того, операция "res3" показывает более высокую производительность при измерении с помощью функции "gputimeit". Дополнительную информацию об этом см. В прилагаемом файле testScript2.m. Я также добавил "test_v2.m", который является модификацией функции "test.m" в вашей статье "Переполнение стека".
Спасибо, что сообщили мне об этом. Я хотел бы извиниться за любые неудобства, вызванные этой проблемой. Я создал внутренний отчет об ошибке, чтобы сообщить разработчикам MATLAB об этом поведении. Они могут обеспечить исправление для этого в будущей версии MATLAB.
Поскольку у вас возникли дополнительные вопросы о сравнении производительности графического процессора с использованием "gputimeit" и "tic" и "toc", я просто хотел предложить одно предложение, о котором упомянули ранее разработчики компьютеров MATLAB. Как правило, полезно также "wait (gpuDevice)" перед операциями "tic", чтобы гарантировать, что операции GPU из предыдущих строк не перекрываются при измерении для следующей строки. Например, в следующих строках:
b=gpuArray(rand(n));
tic; res1=a*b*a; wait(gpuDevice); toc
если "wait (gpuDevice)" не вызывается до "tic", некоторое время, затраченное на создание массива "b" из предыдущей строки, может перекрываться и подсчитываться за время, затраченное на выполнение выражения "res1". Это было бы предпочтительнее:
b=gpuArray(rand(n));
wait(gpuDevice); tic; res1=a*b*a; wait(gpuDevice); toc
Помимо этого, я не вижу никаких конкретных проблем в том, как вы используете функции "tic" и "toc". Однако обратите внимание, что использование "gputimeit" обычно рекомендуется с использованием "tic" и "toc" непосредственно для профилирования, связанного с GPU.
Я продолжу и закрываю этот случай, но, пожалуйста, дайте мне знать, если у вас возникнут дополнительные вопросы.
%testscript2.m
n = 2000;
a = gpuArray(sprand(n, n, 0.01));
b = gpuArray(rand(n));
gputimeit(@()transpose_mult_fun(a, b))
gputimeit(@()transpose_mult_fun_2(a, b))
function out = transpose_mult_fun(in1, in2)
i1 = transpose(in1);
i2 = transpose(in1*in2);
out = transpose(i1*i2);
end
function out = transpose_mult_fun_2(in1, in2)
out = transpose(transpose(in1)*transpose(in1*in2));
end
,
function test_v2
clear
%% transposed expression
n = 2000;
rng('default');rng(1);
a = sprand(n, n, 0.1);
b = rand(n, n);
a = gpuArray(a);
b = gpuArray(b);
tic;
c1 = gather(transpose( transpose(a) * transpose(a * b) ));
disp(['time for (a''*(a*b)'')'': ' , num2str(toc),'s'])
clearvars -except c1
%% non-transposed expression
rng('default');
rng(1)
n = 2000;
a = sprand(n, n, 0.1);
b = rand(n, n);
a = gpuArray(a);
b = gpuArray(b);
tic;
c2 = gather(a * b * a);
disp(['time for a*b*a: ' , num2str(toc),'s'])
disp(['error = ',num2str(max(max(abs(c1-c2))))])
%% sliced equivalent
rng('default');
rng(1)
n = 2000;
a = sprand(n, n, 0.1);
b = rand(n, n);
a = gpuArray(a);
b = gpuArray(b);
tic;
intermediate1 = transpose(a);
intermediate2 = transpose(a * b);
c3 = gather(transpose( intermediate1 * intermediate2 ));
disp(['time for split equivalent: ' , num2str(toc),'s'])
disp(['error = ',num2str(max(max(abs(c1-c3))))])
end