Понимание того, как работает интерпретатор MATLAB

#matlab #matrix #indexing #interpreter

#matlab #матрица #индексирование #интерпретатор

Вопрос:

Я пытаюсь ускорить код MATLAB, которому необходимо получить доступ к некоторым членам a(i,j) большой матрицы a в цикле for. Есть части, в которых один термин может потребоваться в пяти или более различных вычислениях. В этих случаях код присваивает термин a(i,j) другой переменной k .

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

Эти результаты могут быть воспроизведены в простой тестовой функции:

 r = 5e6;

i = 50;
j = 50000;

a = zeros(i,j);

% 

tic
for ii = 1:r
    b = a(i,j) a(i,j) a(i,j) a(i,j) a(i,j);
end
toc

%

tic
for ii = 1:r
    k = a(i,j);
    b = k k k k k;
end
toc
  

Первый код занимает в 3,5 раза больше времени, чем второй.

Должен ли MATLAB быть настолько медленным для доступа к данным из матрицы размером ~ 20 Мб?

РЕДАКТИРОВАТЬ 1:

Очевидно, что после ответа Криса Луэнго возникла проблема с установкой MATLAB, с которой я работаю (R2019a). Предыдущий результат был получен с помощью M-файла.

Следующий код выдает выходные данные. Кажется, что компиляции вообще нет.

 r = 5e6;

i = 50;
j = 50000;

a = zeros(i,j);

aux_rgb = lines(2);

figure('Color','White','Name','Code with drawnow'); hold on;
legend('location','bestoutside'); ylim([0,1.05]);
xlabel('number of terms in summation');
ylabel('relative time spent');

h1 = animatedline(NaN,NaN,'LineWidth',2.5,'Color',aux_rgb(1,:),'DisplayName','a(i,j)');
h2 = animatedline(NaN,NaN,'LineWidth',2.5,'Color',aux_rgb(2,:),'DisplayName','k');

%%

n = 1;

t1 = tic;
for ii = 1:r
    b = a(i,j);
end
t1 = toc(t1);
addpoints(h1,n,t1/t1);

t2 = tic;
for ii = 1:r
    k = a(i,j);
    b = k;
end
t2 = toc(t2);
addpoints(h2,n,t2/t1);

drawnow

%%

n = 2;

t1 = tic;
for ii = 1:r
    b = a(i,j) a(i,j);
end
t1 = toc(t1);
addpoints(h1,n,t1/t1);

t2 = tic;
for ii = 1:r
    k = a(i,j);
    b = k k;
end
t2 = toc(t2);
addpoints(h2,n,t2/t1);

drawnow

%%

n = 3;

t1 = tic;
for ii = 1:r
    b = a(i,j) a(i,j) a(i,j);
end
t1 = toc(t1);
addpoints(h1,n,t1/t1);

t2 = tic;
for ii = 1:r
    k = a(i,j);
    b = k k k;
end
t2 = toc(t2);
addpoints(h2,n,t2/t1);

drawnow

%%

n = 4;

t1 = tic;
for ii = 1:r
    b = a(i,j) a(i,j) a(i,j) a(i,j);
end
t1 = toc(t1);
addpoints(h1,n,t1/t1);

t2 = tic;
for ii = 1:r
    k = a(i,j);
    b = k k k k;
end
t2 = toc(t2);
addpoints(h2,n,t2/t1);

drawnow

%%

n = 5;

t1 = tic;

for ii = 1:r
    b = a(i,j) a(i,j) a(i,j) a(i,j) a(i,j);
end
t1 = toc(t1);
addpoints(h1,n,t1/t1);

t2 = tic;
for ii = 1:r
    k = a(i,j);
    b = k k k k k;
end
t2 = toc(t2);
addpoints(h2,n,t2/t1);

drawnow
  

РЕДАКТИРОВАТЬ 2:

Снова следуя ответу Криса Луэнго, это результат, полученный с помощью функции M-file (а не скрипта M-file). Теперь компиляция выполняет свою работу.

Ответ №1:

Существует разница в выполнении кода путем копирования-вставки в командную строку или внутри функции. Я вижу:

 Elapsed time is 0.062195 seconds.
Elapsed time is 0.034381 seconds.
  

при копировании-вставке в командной строке и

 Elapsed time is 0.024922 seconds.
Elapsed time is 0.025392 seconds.
  

если я создам M-файл функции с тем же кодом в нем и запущу функцию (в M-файле функции есть строка, начинающаяся с function ключевого слова в качестве первой строки без комментариев). В этом случае два цикла одинаково быстры (первый может быть немного быстрее, но разница невелика).

Вы также заметите, что оба цикла выполняются быстрее, чем при копировании-вставке в командной строке. При запуске функции функция компилируется, а затем выполняется (это называется «компиляцией точно в срок» или JIT). Этот компилятор способен оптимизировать несколько операций индексирования, эффективно индексируя только один раз.

В командной строке компиляция не происходит, и поэтому местоположение индекса вычисляется пять раз, а значение извлекается пять раз.

Предполагается, что M-файлы сценариев (M-файлы, которые не начинаются с function ключевого слова) будут скомпилированы JIT, но, похоже, это происходит не всегда, по крайней мере, не с таким же количеством оптимизаций.

Комментарии:

1. Я согласен с ответом Криса здесь. Я бы также упомянул, что, по моему мнению, причина , по которой это работает, заключается в том, что когда вы работаете a(i,j) в Matlab, вы не просто ссылаетесь на примитивный элемент внутри массива (как это было бы в C или Java), но вы фактически создаете новый mxArray размером 1 на 1, содержащий это значение, которое имеет накладные расходы. Таким образом, ожидается, что выполнение k = a(i,j); и повторное k использование должны быть быстрее, чем повторные a(i,j) ссылки при обычных обстоятельствах. Только JIT может оптимизировать это.