深入了解 MATLAB 解释器的工作原理
Insights about how MATLAB interpreter works
我正在尝试加速需要在 for 循环中访问大型矩阵 a
的某些项 a(i,j)
的 MATLAB 代码。有些部分可能需要在五次或更多次不同的计算中使用一项。在这些情况下,代码将术语 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 Mb 的矩阵访问数据应该那么慢吗?
编辑 1:
根据 Cris Luengo 的回答,显然我正在使用的 MATLAB 安装 (R2019a) 存在问题。之前的结果是通过一个M文件得到的
以下代码生成 output。好像根本就没有编译。
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:
再次按照Cris Luengo的回答,就是output通过M文件函数(不是M文件脚本)得到的。现在编译完成了它的工作。
将 运行ning 代码 copy-pasting 放入命令行或函数内部是有区别的。我看到了:
Elapsed time is 0.062195 seconds.
Elapsed time is 0.034381 seconds.
当在命令行中copy-pasting,并且
Elapsed time is 0.024922 seconds.
Elapsed time is 0.025392 seconds.
如果我创建一个函数 M-file 并在其中使用相同的代码 运行 该函数(函数 M-file 有一行以 function
关键字开头第一个 non-comment 行)。在这种情况下,两个循环同样快(第一个可能稍微快一点,但差异很小)。
您还会注意到,这两个循环 运行 比命令行中的 copy-pasting 快。当 运行 编译一个函数时,该函数被编译然后执行(这被称为“just-in-time 编译”,或 JIT)。该编译器能够优化多个索引操作,仅有效索引一次。
在命令行中,没有发生编译,因此索引位置被计算了五次,值被检索了五次。
脚本 M-files(M-files 不以 function
关键字开头)应该得到 JIT-compiled,但这似乎并不总是发生,至少不会有相同数量的优化。
我正在尝试加速需要在 for 循环中访问大型矩阵 a
的某些项 a(i,j)
的 MATLAB 代码。有些部分可能需要在五次或更多次不同的计算中使用一项。在这些情况下,代码将术语 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 Mb 的矩阵访问数据应该那么慢吗?
编辑 1:
根据 Cris Luengo 的回答,显然我正在使用的 MATLAB 安装 (R2019a) 存在问题。之前的结果是通过一个M文件得到的
以下代码生成 output。好像根本就没有编译。
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:
再次按照Cris Luengo的回答,就是output通过M文件函数(不是M文件脚本)得到的。现在编译完成了它的工作。
将 运行ning 代码 copy-pasting 放入命令行或函数内部是有区别的。我看到了:
Elapsed time is 0.062195 seconds.
Elapsed time is 0.034381 seconds.
当在命令行中copy-pasting,并且
Elapsed time is 0.024922 seconds.
Elapsed time is 0.025392 seconds.
如果我创建一个函数 M-file 并在其中使用相同的代码 运行 该函数(函数 M-file 有一行以 function
关键字开头第一个 non-comment 行)。在这种情况下,两个循环同样快(第一个可能稍微快一点,但差异很小)。
您还会注意到,这两个循环 运行 比命令行中的 copy-pasting 快。当 运行 编译一个函数时,该函数被编译然后执行(这被称为“just-in-time 编译”,或 JIT)。该编译器能够优化多个索引操作,仅有效索引一次。
在命令行中,没有发生编译,因此索引位置被计算了五次,值被检索了五次。
脚本 M-files(M-files 不以 function
关键字开头)应该得到 JIT-compiled,但这似乎并不总是发生,至少不会有相同数量的优化。