MATLAB 中的 Copy-on-Write 和 varargin

问题描述

MATLAB 文档的 Avoid Unnecessary Copies of Data 部分包含以下语句:

写时复制

如果函数修改输入参数,MATLAB 不会复制输入变量中包含的值。

在这种情况下没有关于 varargin 的词。我试图制定一个能够监控内存使用情况的函数,但没有成功。所以我在这里问:写时复制功能是否适用于 varargin?

假设函数 function Y = f(x,y,z)函数 function Y = f(varargin)。在第一种情况下,函数调用 f(a,b,c) 不会复制 abc(无论变量的类型如何)。在第二种情况下,函数调用 f(a,c) 的行为不清楚。 MATLAB 会在不显式创建元胞数组的情况下将 varargin{1} 指向 a,将 varargin{2} 指向 b 并将 varargin{3} 指向 c,还是 {{ 1}} vararginab 的显式串联(因此内存将存储元胞数组内三个变量的副本)?

解决方法

varargin 是一个元胞数组。当您将对象放入元胞数组时,该对象并未真正被复制,但其引用计数会增加:

a = [1 2 3];
b = 5;
c = {4,6};
varargin = {a,b,c};

这里只是增加了 abc 指向的对象的引用计数。当你这样做时:

varargin{1}(2) = 7;

因为它想写入 a 所指向的对象,所以它制作了该数组对象的副本并将新数组的第二个元素设置为 7。新数组放置在 varargin 的第一个单元格中,并且 a 指向的对象的引用计数减少。但是,MATLAB jit 编译器可能会进行更多优化,并且可能会就地创建变量,因此根本不会创建元胞数组。另一种可能的优化可能与标量等小对象有关。它们是廉价的对象,可以廉价地复制,而且它们可能没有引用计数。

,

作为 @rahnema1 stated,MATLAB 的写时复制机制(也称为延迟复制)适用于每个副本,而不仅仅是函数参数。

证明这一点的一种方法是使用以下从 Yair's Undocumented MATLAB Blog 修改的 MEX 文件:

#include "mex.h"
#include <cstdint>
void mexFunction( int /*nlhs*/,mxArray* plhs[],int nrhs,mxArray const* prhs[]) {
   if (nrhs < 1) mexErrMsgTxt("One input required.");
   plhs[0] = mxCreateNumericMatrix(1,1,mxUINT64_CLASS,mxREAL);
   std::uint64_t* out = static_cast<std::uint64_t*>(mxGetData(plhs[0]));
   out[0] = reinterpret_cast<std::uint64_t>(mxGetData(prhs[0]));
}

您可以将其另存为 getaddr.cpp 并使用 mex getaddr.cpp 进行编译。您现在有了一个函数,可以显示数组的数据存储地址。

例如,如果我们制作一个数组的副本,该副本将具有相同的数据地址。当我们再写入副本时,它的数据地址会发生变化:

>> a=zeros(5);
>> getaddr(a)
ans =
  uint64
   105553130112928
>> a(1)=1;
>> getaddr(a)
ans =
  uint64
   105553130112928
>> b=a;
>> getaddr(b)
ans =
  uint64
   105553130112928
>> b(1)=4;
>> getaddr(b)
ans =
  uint64
   105553130078944

元胞数组也是如此,这与问题直接相关,因为输入参数收集在元胞数组 varargin 中:

>> a=zeros(5);
>> b=zeros(8);
>> v={a,b};
>> getaddr(a)
ans =
  uint64
   105553130246144
>> getaddr(v{1})
ans =
  uint64
   105553130246144

请注意,元胞数组只不过是数据类型为“数组”的数组,因此可以包含任何类型的数组作为其元素。基本上它是一个指向其他数组的指针数组。