结果可能有点误导。当您将两个空矩阵相乘时,生成的矩阵不会立即“分配”和“初始化”,而是推迟到您第一次使用它(有点像惰性求值)。
当索引越界以增长变量时,这同样适用,在数字数组的情况下,用零填充任何缺失的条目(我稍后讨论非数字情况)。当然,以这种方式增长矩阵不会覆盖现有元素。
因此,虽然它可能看起来更快,但您只是在延迟分配时间,直到您真正第一次使用矩阵。最后,您将获得类似的时间安排,就好像您从一开始就进行了分配一样。
与其他一些替代方案相比,显示此行为的示例:
N = 1000;
clear z
tic, z = zeros(N,N); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
clear z
tic, z = zeros(N,0)*zeros(0,N); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
clear z
tic, z(N,N) = 0; toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
clear z
tic, z = full(spalloc(N,N,0)); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
clear z
tic, z(1:N,1:N) = 0; toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
clear z
val = 0;
tic, z = val(ones(N)); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
clear z
tic, z = repmat(0, [N N]); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
结果表明,如果您将每种情况下两条指令的经过时间相加,您最终会得到相似的总时间:
// zeros(N,N)
Elapsed time is 0.004525 seconds.
Elapsed time is 0.000792 seconds.
// zeros(N,0)*zeros(0,N)
Elapsed time is 0.000052 seconds.
Elapsed time is 0.004365 seconds.
// z(N,N) = 0
Elapsed time is 0.000053 seconds.
Elapsed time is 0.004119 seconds.
其他时间是:
// full(spalloc(N,N,0))
Elapsed time is 0.001463 seconds.
Elapsed time is 0.003751 seconds.
// z(1:N,1:N) = 0
Elapsed time is 0.006820 seconds.
Elapsed time is 0.000647 seconds.
// val(ones(N))
Elapsed time is 0.034880 seconds.
Elapsed time is 0.000911 seconds.
// repmat(0, [N N])
Elapsed time is 0.001320 seconds.
Elapsed time is 0.003749 seconds.
这些测量值在毫秒内太小,可能不是很准确,所以你可能想在循环中运行这些命令几千次并取平均值。有时运行保存的 M 函数比运行脚本或在命令提示符下更快,因为某些优化只会以这种方式发生......
无论哪种方式分配通常只进行一次,所以谁在乎它是否需要额外的 30 毫秒 :)
在元胞数组或结构数组中可以看到类似的行为。考虑以下示例:
N = 1000;
tic, a = cell(N,N); toc
tic, b = repmat({[]}, [N,N]); toc
tic, c{N,N} = []; toc
这使:
Elapsed time is 0.001245 seconds.
Elapsed time is 0.040698 seconds.
Elapsed time is 0.004846 seconds.
请注意,即使它们都相等,它们占用的内存量也不同:
>> assert(isequal(a,b,c))
>> whos a b c
Name Size Bytes Class Attributes
a 1000x1000 8000000 cell
b 1000x1000 112000000 cell
c 1000x1000 8000104 cell
事实上,这里的情况有点复杂,因为 MATLAB 可能为所有单元共享同一个空矩阵,而不是创建多个副本。
元胞数组a
实际上是一个未初始化的元胞数组(一个 NULL 指针数组),而b
元胞数组是一个元胞数组,其中每个元胞都是一个空数组[]
(在内部并且由于数据共享,只有第一个元胞b{1}
指向,[]
而其余所有元胞都有对第一个单元格的引用)。最终数组c
类似于a
(未初始化的单元格),但最后一个数组包含一个空的数字矩阵[]
。
我查看了libmx.dll
(使用Dependency Walker工具)导出的 C 函数列表,发现了一些有趣的东西。
有用于创建未初始化数组的未记录函数:mxCreateUninitDoubleMatrix
、mxCreateUninitNumericArray
和mxCreateUninitNumericMatrix
. 事实上有一个关于File Exchange的提交利用了这些功能来提供更快的替代zeros
功能。
存在一个未记录的函数,称为mxFastZeros
. 在网上搜索,我可以看到你也在 MATLAB Answers 上交叉发布了这个问题,那里有一些很好的答案。James Tursa(以前是 UNINIT 的同一作者)举例说明了如何使用这个未记录的函数。
libmx.dll
链接到tbbmalloc.dll
共享库。这是英特尔 TBB可扩展内存分配器。该库提供了针对并行应用程序优化的等效内存分配函数 ( malloc
, calloc
, )。free
请记住,许多 MATLAB 函数是自动多线程zeros(..)
的,所以如果是多线程的并且一旦矩阵大小足够大就使用英特尔的内存分配器,我不会感到惊讶(这是Loren Shure最近的评论,证实了这一事实)。
关于内存分配器的最后一点,您可以在 C/C++ 中编写类似的基准测试,类似于@PavanYalamanchili所做的,并比较各种可用的分配器。像这样的东西。请记住,MEX 文件的内存管理开销稍高,因为 MATLAB 使用mxCalloc
、mxMalloc
或函数自动释放在 MEX 文件中分配的任何内存mxRealloc
。对于它的价值,过去可以在旧版本中更改内部内存管理器。
编辑:
这是一个更彻底的基准来比较所讨论的替代方案。它具体表明,一旦强调使用整个分配矩阵,这三种方法都是平等的,差异可以忽略不计。
function compare_zeros_init()
iter = 100;
for N = 512.*(1:8)
% ZEROS(N,N)
t = zeros(iter,3);
for i=1:iter
clear z
tic, z = zeros(N,N); t(i,1) = toc;
tic, z(:) = 9; t(i,2) = toc;
tic, z = z + 1; t(i,3) = toc;
end
fprintf('N = %4d, ZEROS = %.9f\n', N, mean(sum(t,2)))
% z(N,N)=0
t = zeros(iter,3);
for i=1:iter
clear z
tic, z(N,N) = 0; t(i,1) = toc;
tic, z(:) = 9; t(i,2) = toc;
tic, z = z + 1; t(i,3) = toc;
end
fprintf('N = %4d, GROW = %.9f\n', N, mean(sum(t,2)))
% ZEROS(N,0)*ZEROS(0,N)
t = zeros(iter,3);
for i=1:iter
clear z
tic, z = zeros(N,0)*zeros(0,N); t(i,1) = toc;
tic, z(:) = 9; t(i,2) = toc;
tic, z = z + 1; t(i,3) = toc;
end
fprintf('N = %4d, MULT = %.9f\n\n', N, mean(sum(t,2)))
end
end
以下是 100 次迭代中随着矩阵大小增加的平均时间。我在 R2013a 中进行了测试。
>> compare_zeros_init
N = 512, ZEROS = 0.001560168
N = 512, GROW = 0.001479991
N = 512, MULT = 0.001457031
N = 1024, ZEROS = 0.005744873
N = 1024, GROW = 0.005352638
N = 1024, MULT = 0.005359236
N = 1536, ZEROS = 0.011950846
N = 1536, GROW = 0.009051589
N = 1536, MULT = 0.008418878
N = 2048, ZEROS = 0.012154002
N = 2048, GROW = 0.010996315
N = 2048, MULT = 0.011002169
N = 2560, ZEROS = 0.017940950
N = 2560, GROW = 0.017641046
N = 2560, MULT = 0.017640323
N = 3072, ZEROS = 0.025657999
N = 3072, GROW = 0.025836506
N = 3072, MULT = 0.051533432
N = 3584, ZEROS = 0.074739924
N = 3584, GROW = 0.070486857
N = 3584, MULT = 0.072822335
N = 4096, ZEROS = 0.098791732
N = 4096, GROW = 0.095849788
N = 4096, MULT = 0.102148452