Matlab分配A=zeros(N)与A(N,N)=0在性能结果方面产生奇怪的结果。

6 浏览
0 Comments

Matlab分配A=zeros(N)与A(N,N)=0在性能结果方面产生奇怪的结果。

我惊讶地发现了Matlab处理空矩阵的奇怪方式(在我看来)。例如,如果两个空矩阵相乘,结果是:\n

zeros(3,0)*zeros(0,3)
ans =
 0     0     0
 0     0     0
 0     0     0

\n现在,这已经让我惊讶了,但是一个快速搜索让我找到了上面的链接,我得到了一个关于为什么会发生这种情况的有些扭曲逻辑的解释。\n然而,没有什么能让我为以下观察做好准备。我问自己,这种乘法与只使用zeros(n)函数相比效率如何,比如用于初始化的目的?我使用了timeit来回答这个问题:\n

f=@() zeros(1000)
timeit(f)
ans =
    0.0033

\n与:\n

g=@() zeros(1000,0)*zeros(0,1000)
timeit(g)
ans =
    9.2048e-06

\n两者都得到了相同的结果,即一个大小为1000x1000、元素全为零、类型为double的矩阵,但是空矩阵相乘的方法要快大约350倍!(使用tictoc以及循环得到了类似的结果)\n这是怎么回事?是timeittic,toc在骗人还是我找到了一种更快的初始化矩阵的方法?\n(这是在win7-64机器上的matlab 2012a上完成的,intel-i5 650 3.2Ghz...)\n编辑:\n阅读了您的反馈后,我更仔细地研究了这个奇怪的现象,并在两台不同的计算机上进行了测试(虽然都是使用的matlab 2012a)。我编写了一个代码来检查运行时间与矩阵大小n的关系。结果如下所示:\n\"enter\n生成这个图像的代码与之前使用的timeit相同,但是使用tictoc的循环也会得到相同的结果。所以,对于较小的大小,zeros(n)是可比较的。然而,在n=400左右,空矩阵相乘的性能出现了跳跃。我用来生成这个图像的代码是:\n

n=unique(round(logspace(0,4,200)));
for k=1:length(n)
    f=@() zeros(n(k));
    t1(k)=timeit(f);
    g=@() zeros(n(k),0)*zeros(0,n(k));
    t2(k)=timeit(g);
end
loglog(n,t1,'b',n,t2,'r');
legend('zeros(n)','zeros(n,0)*zeros(0,n)',2);
xlabel('matrix size (n)'); ylabel('time [sec]');

\n你们有没有也有这样的经历?\n编辑 #2:\n偶然间,不需要使用空矩阵相乘也可以得到这种效果。你只需要简单地执行以下操作:\n

z(n,n)=0;

\n其中n>前面图表中看到的某个阈值矩阵大小,就能得到与空矩阵相乘完全相同的效率曲线(再次使用timeit)。 \n\"enter\n这里有一个改善代码效率的例子:\n

n = 1e4;
clear z1
tic
z1 = zeros( n ); 
for cc = 1 : n
    z1(:,cc)=cc;
end
toc % 耗时为0.445780秒。
%%
clear z0
tic
z0 = zeros(n,0)*zeros(0,n);
for cc = 1 : n
    z0(:,cc)=cc;
end
toc % 耗时为0.297953秒。

\n然而,使用z(n,n)=0;zeros(n)的情况相似。

0
0 Comments

Matlab中的矩阵预分配是一项重要的技术,可以提高代码的性能。然而,在使用`zeros(M, N)`进行矩阵预分配时,实际上并不是最高效的方法。这个问题是由Yair Altman在他的一篇文章中发现的。

在这篇文章中,Yair Altman对比了使用`x = zeros(M,N)`和`clear x, x(M,N) = 0`两种矩阵预分配方法的性能差异。他发现,后一种方法的运行速度约快500倍。根据他的解释,第二种方法只是简单地创建一个M×N的矩阵,并且矩阵中的元素会自动初始化为0。而第一种方法则是先创建一个具有自动零元素的x,然后再将每个元素赋值为0,这是一种多余的操作,需要更多的时间。

在空矩阵乘法的情况下,例如你在问题中提到的情况,MATLAB会假设结果是一个M×N的矩阵,并且会预分配一个M×N的矩阵。因此,输出矩阵会自动初始化为零。由于原始矩阵是空的,所以不会进行进一步的计算,因此输出矩阵的元素保持不变且等于零。

除了Yair Altman的文章,还有一个名为"uninit"的FEX文件,可以优化内存预分配的操作。然而,这并不能解释为什么在n大于1000时会出现不同的行为。这可能是与系统相关的行为。有人在C++中复现了这种行为。

通过使用`clear x, x(M,N) = 0`进行矩阵预分配,可以获得更高的性能。然而,对于空矩阵乘法的情况,预分配的方法并不会产生不同的结果。这个问题的解决方法需要根据具体的情况进行调整和优化。

0
0 Comments

Matlab中使用`zeros(N)`和`A(N,N)=0`进行矩阵赋值在性能上可能会产生一些奇怪的结果。这个问题的原因是,当你相乘两个空矩阵时,结果矩阵不会立即"分配"和"初始化",而是在你首次使用它时才会进行分配和初始化(类似于惰性求值)。

当你通过索引超出边界来扩展一个变量时(在数值数组的情况下),它会用0填充任何缺失的条目(我将在后面讨论非数值情况)。当然,通过这种方式扩展矩阵不会覆盖现有的元素。

所以,虽然它可能看起来更快,但实际上你只是延迟了分配时间,直到你首次使用矩阵。最终,你会得到与从开始时进行分配相似的时间。

下面是一个展示这种行为的示例,与一些其他替代方法进行比较:

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`的导出C函数列表(使用Dependency Walker工具),发现了一些有趣的事情。

- 存在用于创建未初始化数组的未记录函数:`mxCreateUninitDoubleMatrix`、`mxCreateUninitNumericArray`和`mxCreateUninitNumericMatrix`。事实上,有一个在File Exchange上的提交利用这些函数提供了一个比`zeros`函数更快的替代方法。

- 存在一个名为`mxFastZeros`的未记录函数。通过在网上搜索,我看到你还在MATLAB Answers上发布了这个问题,那里有一些很好的答案。James Tursa(之前的UNINIT的作者)给出了如何使用这个未记录函数的示例。

- `libmx.dll`链接到了`tbbmalloc.dll`共享库。这是Intel TBB可伸缩内存分配器。该库提供了针对并行应用程序优化的等效内存分配函数(`malloc`、`calloc`、`free`)。请记住,许多MATLAB函数都自动进行多线程处理,所以如果矩阵大小足够大,我不会感到惊讶,如果`zeros(..)`是多线程的,并且在一定程度上使用了Intel的内存分配器(这里是Loren Shure最近评论的一个例子,证实了这一点)。

关于最后一个关于内存分配器的问题,你可以编写一个类似于`C/C++`的基准测试,类似于之前所做的,并比较可用的各种分配器。类似于这样的东西。请记住,MEX文件的内存管理开销略高,因为MATLAB会自动释放使用`mxCalloc`、`mxMalloc`或`mxRealloc`函数在MEX文件中分配的任何内存。就我所知,以前的版本中可以更改内部内存管理器。

编辑:

这里有一个更详细的基准测试来比较讨论的各种方法。它明确显示,一旦你在整个分配的矩阵中使用它,所有三种方法的性能是相同的,差别是微不足道的。

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

下面是随着矩阵大小增加的平均时间。我在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

感谢Amro提供详细的答案。为什么当我使用空矩阵预分配时,我会获得更好的性能呢?就像我在问题的第二个编辑中所看到的那样?这个例子既进行了预分配的零矩阵,又进行了赋值操作,但是空矩阵乘法仍然比常规的`zeros`函数胜出。另外需要注意的是,我需要每秒预分配大约100次的图像进行即时数据分析,所以我尽量优化我的代码...

我认为其他方法并没有真正地将整个矩阵填充为零值,而是将其推迟到需要使用它的时候。我猜测MATLAB在`mxArray`结构中有一种内部标志来指示这种状态。这就是为什么仅仅进行预分配是误导性的。我刚刚添加了一个更全面的比较,看到编辑部分...

另外,如果你需要重复初始化一个矩阵,你可以使用语法`M(:)=0;`而不是每次重新创建数组`M=zeros(..)`,我认为这样稍微快一点...但是,要注意过早的优化 🙂

0
0 Comments

Matlab中有两种方式可以创建一个大小为N的零矩阵,即A=zeros(N)和A(N,N)=0。然而,在某些情况下,这两种方式的性能表现会不同,即A=zeros(N)的运行速度可能会比A(N,N)=0要快。

在上述讨论中,有人发现在较大的矩阵大小下,A=zeros(N)的运行速度要快于A(N,N)=0。这在一些操作系统和版本的Matlab中可能会出现。通过使用C++编写的程序来模拟Matlab创建零矩阵的过程,发现在较大的矩阵大小下,A=zeros(N)使用了较慢的内存分配方式(malloc+memset),而A(N,N)=0仍然使用了较快的内存分配方式(calloc)。

根据实验结果,可以得出以下结论:

- zeros函数在较小的矩阵大小下使用calloc进行内存分配,而在较大的矩阵大小下使用malloc+memset进行内存分配。

- 使用calloc进行内存分配比使用malloc+memset更快。

- 不同的操作系统和Matlab版本可能会导致zeros函数在不同大小的矩阵下使用不同的内存分配方式。

解决这个问题的方法是使用更快的内存分配方式,即使用calloc函数。另外,可以尝试在不同的操作系统和Matlab版本上进行实验,以验证实验结果的可重复性。

总结起来,Matlab中的A=zeros(N)和A(N,N)=0两种方式创建零矩阵的性能表现可能会不同,这取决于矩阵的大小、操作系统和Matlab版本等因素。为了获得更好的性能,建议使用A=zeros(N)来创建零矩阵。

0