22

我想了解 R 在将参数传递给函数、创建变量副本等关于内存使用情况时使用的逻辑。它何时实际创建变量的副本而不是仅传递对该变量的引用?特别是我很好奇的情况是:

f <- function(x) {x+1}
a <- 1
f(a)

a从字面上传递还是对传递的引用?

x <- 1
y <- x

副本参考?什么时候不是这种情况?

如果有人可以向我解释这一点,我将不胜感激。

4

3 回答 3

17

当它传递变量时,总是通过复制而不是通过引用。但是,有时,在实际发生分配之前,您不会得到副本。该过程的真实描述是pass-by-promise。看看文档

?force
?delayedAssign

一个实际的含义是,即使不是不可能,也很难避免需要至少两倍于对象名义占用的 RAM。修改大对象通常需要制作临时副本。

更新:2015 年:我确实(并且确实)同意 Matt Dowle 的观点,即他的 data.table 包提供了另一种分配途径,可以避免复制问题。如果那是请求的更新,那么在提出建议时我不明白。

applyR 3.2.1 最近对和的评估规则进行了更改Reduce。这是参考新闻在这里宣布的:从 lapply 返回匿名函数 - 出了什么问题?

jhetzel 在评论中引用的有趣论文现在在这里

于 2012-05-18T15:41:30.360 回答
9

迟到的答案,但语言设计的一个非常重要的方面,在网络上没有得到足够的覆盖(或至少是通常的来源)。

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"
于 2019-10-15T10:29:12.937 回答
0

@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

于 2020-03-31T04:31:52.043 回答