使用top
,我在以下代码块的注释中指定的特定点手动测量了以下内存使用情况:
x <- matrix(rnorm(1e9),nrow=1e4)
#~15gb
gc()
# ~7gb after gc()
y <- as.vector(x)
gc()
#~15gb after gc()
Run Code Online (Sandbox Code Playgroud)
很明显,这rnorm(1e9)
是一个 ~7gb 的向量,然后被复制以创建矩阵。gc()
删除原始向量,因为它没有分配给任何东西。as.vector(x)
然后强制并将数据复制到向量。
我的问题是,为什么这三个对象不能都指向同一个内存块(至少在一个被修改之前)?矩阵真的只是一个带有一些额外元数据的向量吗?
这是 R 版本 3.6.2
编辑:也在 4.0.3 中进行了测试,结果相同。
你问的问题是推理。这似乎更适合 R-devel,我假设作为回报的答案是“没人知道”。R-source 的相关函数是do_asvector
函数。
继续调用 的源代码,as.vector(matrix(...))
重要的是要注意 的默认参数mode
是any
。这转化为ANYSXP
(见 R 内部)。这让我们找到了复制行为的罪魁祸首(第 1524 行)。
// source reference: do_asvector
...
if(type == ANYSXP || TYPEOF(x) == type) {
switch(TYPEOF(x)) {
case LGLSXP:
case INTSXP:
case REALSXP:
case CPLXSXP:
case STRSXP:
case RAWSXP:
if(ATTRIB(x) == R_NilValue) return x;
ans = MAYBE_REFERENCED(x) ? duplicate(x) : x; // <== evil culprit
CLEAR_ATTRIB(ans);
return ans;
case EXPRSXP:
case VECSXP:
return x;
default:
;
}
...
Run Code Online (Sandbox Code Playgroud)
更进一步,我们可以找到MAYBE_REFERENCED
in的定义src/include/Rinternals.h
,通过挖掘一点我们可以发现它检查是否sxpinfo.named
等于 0 (false) 或不等于 (true)。我在这里猜测的是赋值运算符会<-
增加sxpinfo.named
计数器并因此MAYBE_REFERENCED(x)
返回 TRUE 并且我们得到一个duplicate
(深拷贝)。
这是一个很好的问题。如果我们给了一个mode
不同于any
or的参数class(x)
(与我们的输入类相同),我们会跳过重复的行,我们继续执行函数,直到我们遇到 a ascommon
。所以我挖了一些额外的东西并查看了 的源代码ascommon
,我们可以看到,如果我们尝试list
手动转换为(设置mode = "list"
),则ascommon
只会调用shallowDuplicate
.
// Source reference: ascommon
---
if ((type == LISTSXP) &&
!(TYPEOF(u) == LANGSXP || TYPEOF(u) == LISTSXP ||
TYPEOF(u) == EXPRSXP || TYPEOF(u) == VECSXP)) {
if (MAYBE_REFERENCED(v)) v = shallow_duplicate(v); // <=== ascommon duplication behaviour
CLEAR_ATTRIB(v);
}
return v;
}
---
Run Code Online (Sandbox Code Playgroud)
因此,可以想象对duplicate
in的调用do_asvector
可以替换为对 的调用shallow_duplicate
。也许一个“更好的安全比遗憾”时码(根据该评论之前,R-2.13.0最初实施的选择策略的源代码),或者可能存在的类型之一的情景不处理ascommon
该需要深拷贝。
现在,如果我们mode='list'
在没有赋值的情况下设置或传递列表,我将测试该函数是否进行深度复制。在任何一种情况下,将后续问题发送到 R-devel 邮件列表都不是一个坏主意。
<-
行为我冒昧地证实了我的怀疑,并查看了<-
. 我之前说过我假设<-
incremented sxpinfo.named
,我们可以通过查看do_set
( 的 c 源代码<-
)来确认这一点。当将x <- ...
x赋值为 a 时SYMSXP
,我们可以看到源代码调用了INCREMENT_NAMED
,而后者又调用了SET_NAMED(x, NAMED(X) + 1)
. 所以在其他条件相同的情况下,我们应该看到复制行为,x <- matrix(...); y <- as.vector(x)
而我们不应该看到for y <- as.vector(matrix(...))
。