为什么 MATLAB 的逐元素求幂会加速 512 个元素?
Why does MATLAB's element-wise exponentiation speed up for 512 elements?
当数组的大小变为 512 时,MATLAB 用于计算常量基数和指数数组的逐元素指数的幂函数变得明显更快。我希望看到计算时间随着输入大小的增加而增加,但是,当指数数组中有 512 个元素时,会出现明显下降。这是一个示例代码
x_list = 510:514;
for i = 1:numel(x_list)
x = x_list(i);
tic
for j = 1:10000
y = power(2,1:x);
end
toc
end
代码的输出是
Elapsed time is 0.397649 seconds.
Elapsed time is 0.403687 seconds.
Elapsed time is 0.318293 seconds.
Elapsed time is 0.238875 seconds.
Elapsed time is 0.175525 seconds.
这里发生了什么?
这与计算功率的数字的大小有关,而不是容器的大小。
如果您使用随机数,对于不同的容器大小,不会观察到时间跳跃:
x = 450:1550;
y = zeros(numel(x), 1);
X = rand(1, 10000);
for i = 1:length(x)
f = @() 2 .^ X(1:x(i));
y(i) = timeit(f);
end
figure()
plot(x, y)
因此问题一定出在对非常大的数字的计算上。
我首先认为这可能与溢出有关,但溢出发生在 2 ^ 1024 == inf
处,正如 MATLAB 遵循的 IEEE standards 所指示的,我认为对于 inf
这本来是比实际计算数字快得多。
这得到以下基准的支持,其中数组的大小保持不变:
x = 450:1550;
y = zeros(numel(x), 1);
X = rand(1, 10000);
for i = 1:length(x)
f = @() 2 .^ (ones(1, 500) * x(i));
y(i) = timeit(f);
end
figure()
plot(x, y)
当 2 ^ 512
而不是 2 ^ 1024
时,为什么这可能与您的设置相关,我真的不明白。
(请注意,我使用 2 .^ ...
而不是 power(2, ...)
,但结果是一样的。)
此外,运行 @CrisLuengo 的代码在我的系统中并没有真正重现任何跳跃。
x = 500:540;
t = zeros(size(x));
for ii = 1:numel(x)
%m = 1:x(ii);
m = 500*rand(1,x(ii));
t(ii) = timeit(@()power(2,m));
end
plot(x,t)
目前所有的证据都表明峰值与 JIT 有关 latency/warm-up。
我看到使用随机数作为指数的效果与我看到使用范围 1:n
:
中的整数相同
x = 500:540;
t = zeros(size(x));
for ii = 1:numel(x)
%m = 1:x(ii);
m = 500*rand(1,x(ii));
t(ii) = timeit(@()power(2,m));
end
plot(x,t)
当强制 MATLAB 使用带有 maxNumCompThreads(1)
的单线程和 运行 上面的代码时,我看到了这张图(注意 y-axis,峰值只是噪声):
在我看来,MATLAB 使用单个内核来计算 511 个值的指数,如果矩阵较大,则启动所有内核。使用多线程会产生开销,对于小数组不值得这样做。开销与时间节省平衡的确切点取决于许多因素,因此 hard-coding 何时切换到多线程计算的固定阈值会导致在具有不同特征的系统上执行时间的跳跃确定阈值的系统。
请注意,@norok2 没有看到同样的跳跃,因为在他们的系统上 。
这里使用 4 核 Windows 机器 运行 MATLAB R2018a 对 进行了一些确认。我首先测试了以下代码,以表明指数的特定值不是跳转的罪魁祸首:
t = zeros(4, 1000);
for p = 1:size(t, 1)
for n = 1:size(t, 2)
t(p, n) = timeit(@() power(2, (2.^(p-1)).*ones(1, n)));
end
end
结果如下:
对于指数为 1(return 相同值)或 2(return 值乘以自身)的退化边缘情况,计算运行得更快,正如预期的那样。但是,与数组大小超过 512 时指数 4 和 8 的计算时间减少相比,在 512 或更大的数组大小处的跳跃表明这些边缘情况 添加 开销. 较大的指数值简单地再现了上面的曲线。
然后我 运行 再进行两项测试:一项的数组大小在 1 到 511 之间,另一项的数组大小在 512 到 1024 之间。这是处理器负载的样子:
处理器 3 在第一次测试中显示出较大的负载峰值,而所有 4 个处理器在第二次测试中都显示出负载峰值。这证实多线程用于 512 或更大的数组大小。这也解释了在较大尺寸的边缘情况下计算速度较慢的原因,因为多线程的开销超过了拆分更简单的计算所提供的加速。
当数组的大小变为 512 时,MATLAB 用于计算常量基数和指数数组的逐元素指数的幂函数变得明显更快。我希望看到计算时间随着输入大小的增加而增加,但是,当指数数组中有 512 个元素时,会出现明显下降。这是一个示例代码
x_list = 510:514;
for i = 1:numel(x_list)
x = x_list(i);
tic
for j = 1:10000
y = power(2,1:x);
end
toc
end
代码的输出是
Elapsed time is 0.397649 seconds.
Elapsed time is 0.403687 seconds.
Elapsed time is 0.318293 seconds.
Elapsed time is 0.238875 seconds.
Elapsed time is 0.175525 seconds.
这里发生了什么?
这与计算功率的数字的大小有关,而不是容器的大小。
如果您使用随机数,对于不同的容器大小,不会观察到时间跳跃:
x = 450:1550;
y = zeros(numel(x), 1);
X = rand(1, 10000);
for i = 1:length(x)
f = @() 2 .^ X(1:x(i));
y(i) = timeit(f);
end
figure()
plot(x, y)
因此问题一定出在对非常大的数字的计算上。
我首先认为这可能与溢出有关,但溢出发生在 2 ^ 1024 == inf
处,正如 MATLAB 遵循的 IEEE standards 所指示的,我认为对于 inf
这本来是比实际计算数字快得多。
这得到以下基准的支持,其中数组的大小保持不变:
x = 450:1550;
y = zeros(numel(x), 1);
X = rand(1, 10000);
for i = 1:length(x)
f = @() 2 .^ (ones(1, 500) * x(i));
y(i) = timeit(f);
end
figure()
plot(x, y)
当 2 ^ 512
而不是 2 ^ 1024
时,为什么这可能与您的设置相关,我真的不明白。
(请注意,我使用 2 .^ ...
而不是 power(2, ...)
,但结果是一样的。)
此外,运行 @CrisLuengo 的代码在我的系统中并没有真正重现任何跳跃。
x = 500:540;
t = zeros(size(x));
for ii = 1:numel(x)
%m = 1:x(ii);
m = 500*rand(1,x(ii));
t(ii) = timeit(@()power(2,m));
end
plot(x,t)
目前所有的证据都表明峰值与 JIT 有关 latency/warm-up。
我看到使用随机数作为指数的效果与我看到使用范围 1:n
:
x = 500:540;
t = zeros(size(x));
for ii = 1:numel(x)
%m = 1:x(ii);
m = 500*rand(1,x(ii));
t(ii) = timeit(@()power(2,m));
end
plot(x,t)
当强制 MATLAB 使用带有 maxNumCompThreads(1)
的单线程和 运行 上面的代码时,我看到了这张图(注意 y-axis,峰值只是噪声):
在我看来,MATLAB 使用单个内核来计算 511 个值的指数,如果矩阵较大,则启动所有内核。使用多线程会产生开销,对于小数组不值得这样做。开销与时间节省平衡的确切点取决于许多因素,因此 hard-coding 何时切换到多线程计算的固定阈值会导致在具有不同特征的系统上执行时间的跳跃确定阈值的系统。
请注意,@norok2 没有看到同样的跳跃,因为在他们的系统上
这里使用 4 核 Windows 机器 运行 MATLAB R2018a 对
t = zeros(4, 1000);
for p = 1:size(t, 1)
for n = 1:size(t, 2)
t(p, n) = timeit(@() power(2, (2.^(p-1)).*ones(1, n)));
end
end
结果如下:
对于指数为 1(return 相同值)或 2(return 值乘以自身)的退化边缘情况,计算运行得更快,正如预期的那样。但是,与数组大小超过 512 时指数 4 和 8 的计算时间减少相比,在 512 或更大的数组大小处的跳跃表明这些边缘情况 添加 开销. 较大的指数值简单地再现了上面的曲线。
然后我 运行 再进行两项测试:一项的数组大小在 1 到 511 之间,另一项的数组大小在 512 到 1024 之间。这是处理器负载的样子:
处理器 3 在第一次测试中显示出较大的负载峰值,而所有 4 个处理器在第二次测试中都显示出负载峰值。这证实多线程用于 512 或更大的数组大小。这也解释了在较大尺寸的边缘情况下计算速度较慢的原因,因为多线程的开销超过了拆分更简单的计算所提供的加速。