结果可能有点误导性。当你将两个空矩阵相乘时,生成的矩阵不会立即"分配"和"初始化",而是要等到第一次使用它时才进行(有点像懒惰求值)。
当索引越界来增加一个变量时,同样适用于此,在数值数组的情况下,会用零填充任何缺失的条目(我随后讨论非数值情况)。当然,这种方式增加矩阵不会覆盖现有元素。
因此,虽然看起来似乎更快,但只是将分配时间推迟到实际首次使用矩阵时。最终,您将具有与从开始进行分配相似的计时。
示例以显示此行为,与几个其他选择进行比较:
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 函数比运行脚本或在命令提示符上运行更快,因为某些优化只能以这种方式进行...无论哪种方式,分配通常只会执行一次,所以即使需要额外的30ms也没关系 :)
类似的行为也可以在单元数组或结构体数组中看到。考虑以下示例:
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
(未初始化单元格),但最后一个单元格包含一个空数值矩阵[]
。
我使用Dependency Walker工具查看了libmx.dll
导出的C函数列表,发现了一些有趣的东西。
有一些未记录的函数可用于创建未初始化的数组:mxCreateUninitDoubleMatrix
、mxCreateUninitNumericArray
和mxCreateUninitNumericMatrix
。事实上,File Exchange上有一个提交使用这些函数提供了比zeros
函数更快的替代方法。
存在一个名为mxFastZeros
的未记录函数。在网上搜索,我发现你也在MATLAB Answers上发布了这个问题,并得到了一些很好的答案。James Tursa(之前的UNINIT作者)给出了一个example,介绍如何使用这个未记录的函数。
libmx.dll
链接了tbbmalloc.dll
共享库。这是Intel TBB可扩展的内存分配器。该库提供了优化并行应用程序的等效内存分配函数(malloc
、calloc
、free
)。请记住,许多MATLAB函数都是automatically multithreaded,因此当矩阵大小足够大时,我不会感到惊讶,如果zeros(..)
是多线程的,并且在使用Intel的内存分配器(这是Loren Shure最近的评论证实了这一事实)。
关于内存分配器的最后一点,您可以编写类似 @PavanYalamanchili 所做的 C/C++ 基准测试,并比较可用的各种分配器。类似于 this。请记住,MEX 文件具有稍高的 内存管理 开销,因为 MATLAB 自动释放使用 mxCalloc
、mxMalloc
或 mxRealloc
函数在 MEX 文件中分配的任何内存。值得一提的是,在旧版本中可以更改内部 内存管理器。
编辑:
这里有一个更全面的基准测试,用于比较讨论的替代方法。它特别显示一旦您强调使用整个分配的矩阵,所有三种方法都是相等的,差异可以忽略不计。
function compare_zeros_init()
iter = 100;
for N = 512.*(1:8)
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)))
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)))
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
zeros
方法显示了与乘法方法相同的行为。 - Cris Luengo