为什么reshape速度如此之快?(剧透:写时复制)

24

我有一个大矩阵A,里面有1GB的双精度数值,当我将其重塑为不同的维度时,速度非常快。

A=rand(128,1024,1024);
tic;B=reshape(A,1024,128,1024);toc

Elapsed time is 0.000011 seconds.

为什么这么快?还有一个观察结果是,在运行代码并存储两个1GB大小的矩阵后,MATLAB使用的内存比应该使用的要少: Memory used by MATLAB: 1878 MB (1.969e+09 bytes)

1个回答

41

好性能的解释

Matlab尽可能使用写时复制。如果你编写像B=A这样的表达式,MATLAB不会复制A,而是两个变量AB都是对同一数据结构的引用。只有当这两个变量中的一个将被修改时,MATLAB才会创建副本。

现在来看reshape的特殊情况。在这里,A和B看起来不同,但在内存中它们是相同的。保存数据的基础数组不受reshape操作的影响,无需移动任何内容:all(A(:)==B(:))。调用reshape时,MATLAB所需要做的就是创建一个新的引用,并使用矩阵的新维度进行注释。 重塑矩阵只不过是创建对输入数据的新引用,并用新的维度进行注释。reshape的运行时间少于1微秒,大约等于执行两个简单赋值B=A所需的时间。对于所有实际应用程序而言,这都是一个零时间操作。

>> tic;for i=1:1000;B=reshape(A,1024,128,1024);end;toc
Elapsed time is 0.000724 seconds.
>> tic;for i=1:1000;B=A;end;toc
Elapsed time is 0.000307 seconds.

目前并不清楚这样的引用到底有多大,但我们可以假设它在几个字节以内。

其他零成本操作

已知具有几乎零成本(运行时和内存)的函数:

  • B=reshape(A,sz)
  • B=A(:)
  • B=A.' - 仅适用于向量
  • B=A' - 仅适用于实数向量,没有属性 complex。使用 .'代替。
  • B=permute(A,p) - 仅适用于all(A(:)==B(:))的情况。1
  • B=ipermute(A,p) - 仅适用于all(A(:)==B(:))的情况。1
  • B=squeeze(A) 1
  • shiftdim - 仅适用于all(A(:)==B(:))的情况,包括以下情况:1
    • 用于删除前导单一维度。
    • 与负的第二个输入一起使用
    • 没有第二个输入参数。

无论它们是否触及内存表示,以下函数都是“昂贵”的(all(A(:)==B(:))为真)

  • 左侧索引:B(1:numel(A))=A; 2
  • (:)之外的右侧索引,包括B=A(1:end);B=A(:,:,:); 2

1 运行时比reshape慢得多,介于1微秒和1毫秒之间。可能是由于某些常量计算开销。内存消耗几乎为零,运行时间与输入大小无关。没有此注释的操作运行时低于1微秒,大致相当于reshape

2在OCTAVE中成本为零

本文最初使用MATLAB 2013b编写,用MATLAB 2019b确认了数据。


非常好的回答!左右侧索引是MATLAB/OCTAVE最强大的功能之一,为什么它“昂贵”?需要比必要的时间更长吗?[我不是指像B(1:numel(A)) = A这样的整个数据复制,这可以通过B=A(或变体)在零时间成本下完成,正如您所解释的那样...]那么,我如何减少例如B(1:lenx:end, j) = A(i, 1:leny:end).'的成本?您知道一种(很多)更快的方法吗? - Fat32
我认为你对 permute 的零成本观点是不正确的。如果你使用一个真正的内存分析器来分析一个大数组的置换,你会发现 permute 实际上首先会进行一次复制。在我的情况下,我有一个 6GB 的数组被置换,这会使内存使用量短暂地增加到 12GB(仅针对此数组)。与 reshape 相比,对于大数组来说,这是一个巨大的问题。 - alwaysmvp45
@alwaysmvp45:如果今天的MATLAB版本比我在2013b中观察到的更糟,我会感到惊讶,但也有可能。您使用的矩阵大小是多少,以及您的置换操作是什么?您是否验证了all(A(:)==B(:))实际上是正确的? - Daniel
我想我误解了那里的意思,但现在我明白了那个条件是多么独特。在什么情况下,您可以对矩阵(或N维数组)进行置换,以使该条件成立?您基本上需要在极其结构化的环境中进行极高量的重复,或者像具有转置的单位矩阵这样的极度简化的示例,即使进行置换也不会获得任何好处。我想不出一个非人为制造的情况,在这种情况下,您可以符合此条件并从置换中获得任何东西。 - alwaysmvp45
为什么它不像Python的实现那样,转置只是数据的一个视图?即使它不是默认设置,考虑到内存高效的转置有多有用,它也应该是一个选项。 - alwaysmvp45
如果你想在Python中使用类似的东西,可以使用NumPy。查看文档以获取步幅信息。 - Daniel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接