我想了解 R 在将参数传递给函数、创建变量副本等关于内存使用情况时使用的逻辑。它何时实际创建变量的副本而不是仅传递对该变量的引用?特别是我很好奇的情况是:
f <- function(x) {x+1}
a <- 1
f(a)
是a
从字面上传递还是对传递的引用?
x <- 1
y <- x
副本参考?什么时候不是这种情况?
如果有人可以向我解释这一点,我将不胜感激。
我想了解 R 在将参数传递给函数、创建变量副本等关于内存使用情况时使用的逻辑。它何时实际创建变量的副本而不是仅传递对该变量的引用?特别是我很好奇的情况是:
f <- function(x) {x+1}
a <- 1
f(a)
是a
从字面上传递还是对传递的引用?
x <- 1
y <- x
副本参考?什么时候不是这种情况?
如果有人可以向我解释这一点,我将不胜感激。
当它传递变量时,总是通过复制而不是通过引用。但是,有时,在实际发生分配之前,您不会得到副本。该过程的真实描述是pass-by-promise。看看文档
?force
?delayedAssign
一个实际的含义是,即使不是不可能,也很难避免需要至少两倍于对象名义占用的 RAM。修改大对象通常需要制作临时副本。
更新:2015 年:我确实(并且确实)同意 Matt Dowle 的观点,即他的 data.table 包提供了另一种分配途径,可以避免复制问题。如果那是请求的更新,那么在提出建议时我不明白。
apply
R 3.2.1 最近对和的评估规则进行了更改Reduce
。这是参考新闻在这里宣布的:从 lapply 返回匿名函数 - 出了什么问题?
jhetzel 在评论中引用的有趣论文现在在这里:
迟到的答案,但语言设计的一个非常重要的方面,在网络上没有得到足够的覆盖(或至少是通常的来源)。
x <- c(0,4,2)
lobstr::obj_addr(x)
# [1] "0x7ff25e82b0f8"
y <- x
lobstr::obj_addr(y)
# [1] "0x7ff25e82b0f8"
注意相同的“内存地址”,即存储对象的内存位置。因此,您可以确认x
并且y
两者都指向相同的标识符。
Hadley Wickham 的 Advanced R 书涉及到这一点:
考虑这段代码:
x <- c(1, 2, 3)
很容易理解为:“创建一个名为 'x' 的对象,其中包含值 1、2 和 3”。不幸的是,这种简化会导致对 R 在幕后实际所做的事情的不准确预测。更准确地说,这段代码做了两件事:
它正在创建一个对象,一个值向量,
c(1, 2, 3)
. 它将该对象绑定到一个名称,x
. 换句话说,对象或值没有名称。它实际上是具有价值的名称。
请注意,它们的内存地址是短暂的,并且随着每个新的 R 会话而变化。
现在这是重要的部分。
在 R 语义中,对象是按值复制的。这意味着修改副本会使原始对象保持不变。由于在内存中复制数据是一项昂贵的操作,因此 R 中的副本尽可能地懒惰。它们仅在实际修改新对象时发生。来源:[R 语言文档][1]
因此,如果我们现在通过将值y
附加到向量来修改的值,y
现在指向不同的“对象”。这与文档所说的“仅在修改新对象时”(惰性)发生的复制操作一致。y
指向与以前不同的地址。
y <- c(y, -3)
print(lobstr::obj_addr(y))
# [1] "0x7ff25e825b48"
@onlyphantom 这非常有帮助!并且对象可以在不被复制的情况下从函数中往返:这就是把我带到这里的原因:
tmp <- function(x, create) {
if(!create){
x
}else{
"new"
}
}
x = c(0,4,2)
y = tmp(x, F)
lobstr::obj_addr(x) == lobstr::obj_addr(y) # y points to x!
这在用自身“替换” x 时有效 - 没有副本!
oldAddr = lobstr::obj_addr(x)
x = tmp(x, F)
lobstr::obj_addr(x) == oldAddr # TRUE!
手册中的这个示例(稍作修改)也提供了有关触发惰性评估的信息
tmp = function(x, label = deparse(x), force=TRUE) {
if(force){
label
}
x <- x + 1
print(label); return(x)
}
tmp(2)
[1] "2"
[1] 3
tmp(2, force=F)
[1] "3"
[1] 3
R 版本 3.6.3