MATLAB中的Copy-on-Write和varargin
MATLAB 文档中有避免不必要的数据副本部分,其中可以找到以下语句:
写时复制
如果函数不修改输入参数,MATLAB 不会复制输入变量中包含的值。
在这种情况下,没有关于 varargin 的词。我试图制定一个能够监控内存使用情况的函数,但没有成功。所以我在这里问:写时复制功能是否适用于 varargin?
假设函数function Y = f(x,y,z)与函数function Y = f(varargin)。在第一种情况下,函数调用f(a,b,c)不会复制a,b和c(无论变量的类型如何)。在第二种情况下,函数调用的行为f(a,b,c)不清楚。将MATLAB点出来varargin{1}到a,varargin{2}要b和varargin{3}以c不明确创建单元阵列,或者是varargin一个明确的串联a,b以及c(因此内存将存储单元阵列内的三个变量的副本)?
回答
乍一看,这是一个更复杂的主题。部分原因是它没有在 MATLAB 文档中完整记录,部分原因是幕后的共享机制多年来发生了变化。首先,我将简要描述什么是 MATLAB 变量。然后我将描述 MATLAB 使用的各种共享机制。最后,我将描述这些共享机制是如何在 MATLAB 幕后使用的。
一个 MATLAB 变量:
MATLAB 变量基本上是一个称为 mxArray 的 C 结构体,其中包含用于保存信息(例如大小、类、存储类和数据指针)的各种字段。这个 C 结构的地址通常称为变量的“结构地址”,数据指针通常称为“Pr”、“Pi”、“Ir”、“Jc”等。 对于更高版本的 MATLAB 复数数据是交错的,没有 Pi 指针。对于固有的数字、逻辑和字符类,数据直接位于 Pr 和 Pi 数据指针(以及用于稀疏变量索引的 Ir 和 Jc 指针)后面。对于 OOP classdef 类变量,
变量共享:
MATLAB 通过以下方式共享变量:
深层复制:有问题的变量不与任何其他变量共享任何内容。
共享数据复制:多个变量可以具有不同的结构地址,但具有相同的数据指针。例如,这通常是直接整个变量赋值或完整变量重构的结果。mxArray (CrossRef) 中曾经有一个字段,它是所有这些变量的链接列表的一部分。更高版本的 MATLAB 只有一个计数器来告诉您列表中有多少变量,但用户无法再访问列表本身。
参考副本:多个变量可以具有完全相同的结构地址。mxArray (refcount) 中的一个字段表示有多少变量共享相同的结构地址。这通常用于单元格或结构变量元素。
父副本:并不是像上面那样本身的副本,而是在嵌套结构和元胞数组中,由于上游共享,变量最终可能会与变量的其他部分或其他变量中的变量共享。在 mxArray 本身中没有这方面的指示。即,CrossRef 和 refcount 看起来是未共享的,但实际上正在发生共享。
句柄复制:如果 OOP classdef 变量是从句柄派生的,那么多个变量本质上是共享的。mxArray 本身不会有任何指示,并且这些变量不遵循正常的“写入时复制”或“延迟复制”规则。
什么时候使用共享?
这是它变得粘稠的地方。这些规则没有公布,而且多年来已经发生了变化。我能做的最好的就是举例:
-- 共享数据复制示例 --
A = B; % direct whole variable assignment (earlier versions of MATLAB)
A{1} = B; % assigning from workspace into cell or struct (earlier versions of MATLAB)
A = reshape(B,whatever); % reshape of full variable
B{1} % cell or struct element in expression or assignment
fun(B); % function arguments are passed as shared data copies of original
A = typecast(B,'whatever'); % later versions of MATLAB only. Early versions did deep copy.
-- 参考副本示例 --
A = B; % direct whole variable assignment (later versions of MATLAB)
A{1} = B{1}; % assignment among cell or struct elements
A = 1:5; % literal assignment of small variable can result in background reference copy
-- 父副本示例 --
A.x = 5; B = A; % A.x is sharing with B.x through the parent A and B sharing.
原问题:
非 Mex 函数参数通过某种类型的复制机制传递到函数中。无论是文字变量还是 varargin,通常都使用共享数据副本(用于显式参数或作为构建 varargin 元胞数组的结果)。我看到的唯一例外是有时嵌套函数可以传递标量变量的深层副本而不是共享数据副本。因此,“写时复制”或“延迟复制”机制适用于函数内部的文字参数和可变参数,因为在这两种情况下,您实际上都在使用共享数据副本(或者可能是后续版本中的引用副本) MATLAB)的函数内的原件。
Mex 函数参数有所不同。旧版本的 MATLAB 总是用于传入原始变量结构地址,但更高版本的 MATLAB 使用与非 Mex 函数相同的规则并传入共享数据副本(尽管标量可能作为深度副本传入)。
所以函数中的“copy-on-write”或“lazy-copy”机制真的没什么特别的。传入了原始变量的共享数据副本或引用副本。因此,如果不对其进行任何更改,则不会在函数内部进行深拷贝。如果您确实更改了参数变量的元素,则将首先进行深度复制(即,非共享)。但这是在 MATLAB 的任何级别都会发生的行为……如果您更改共享变量的元素,则必须先进行深拷贝。无论您是否在函数内部,都适用相同的规则......如果变量是共享的并且您更改了一个元素,那么将首先进行深层复制。
- Probably no way to know this for sure since it isn't documented. We do know that all variables at the MATLAB level are mxArray, and that is what is being passed back & forth at the caller level. Whether the C++ wrapper passes mxArrays around in the background within their proprietary class code, or whether they just extract the relevant information & pointers from the mxArrays at the interface and work with that in the C++ class code is anyone's guess. Since the C++ code is documented to use copy-on-write, we also know that it has access to the sharing information discussed above.
回答
varargin是一个元胞数组。当您将对象放入元胞数组时,该对象并未真正被复制,但其引用计数会增加:
a = [1 2 3];
b = 5;
c = {4, 6};
varargin = {a,b,c};
这里只是被指向对象的引用计数a,b并且c增加。当你这样做时:
varargin{1}(2) = 7;
因为它想写入 指向的对象a,所以它制作了该数组对象的副本并将新数组的第二个元素设置为7。新数组放置在的第一个单元格中,varargin并a减少指向的对象的引用计数。但是,MATLAB jit 编译器可能会进行更多优化,并且可能会就地创建变量,因此根本不会创建元胞数组。另一种可能的优化可能与标量等小对象有关。它们是廉价的对象,可以廉价地复制,而且它们可能没有引用计数。
- The actual behavior of the example above will depend on the MATLAB version. In most versions of MATLAB, varargin{1} will end up being a shared data copy of a, not a reference copy of a. It is only in later versions of MATLAB that you get the reference copy behavior. In either case, though, the copy-on-write rules will apply if you change an element of either one.