JRR*_*JRR 9 r pass-by-reference pass-by-value
这个问题听起来在这里得到了部分回答,但这对我来说还不够具体.当通过引用更新对象以及复制对象时,我想更好地理解.
更简单的例子是矢量增长.下面的代码在R中非常低效,因为在循环之前没有分配内存,并且在每次迭代时都进行了复制.
x = runif(10)
y = c()
for(i in 2:length(x))
y = c(y, x[i] - x[i-1])
Run Code Online (Sandbox Code Playgroud)
分配内存使能保留一些内存,而无需在每次迭代时重新分配内存.因此,这个代码速度极快,特别是对于长向量.
x = runif(10)
y = numeric(length(x))
for(i in 2:length(x))
y[i] = x[i] - x[i-1]
Run Code Online (Sandbox Code Playgroud)
这是我的问题.实际上,当矢量更新时,它确实会移动.有一个副本,如下所示.
a = 1:10
pryr::tracemem(a)
[1] "<0xf34a268>"
a[1] <- 0L
tracemem[0xf34a268 -> 0x4ab0c3f8]:
a[3] <-0L
tracemem[0x4ab0c3f8 -> 0xf2b0a48]:
Run Code Online (Sandbox Code Playgroud)
但在循环中,此副本不会发生
y = numeric(length(x))
for(i in 2:length(x))
{
y[i] = x[i] - x[i-1]
print(address(y))
}
Run Code Online (Sandbox Code Playgroud)
给
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
Run Code Online (Sandbox Code Playgroud)
我理解为什么代码是慢速或快速作为内存分配的函数但我不理解R逻辑.对于同一语句,为什么以及如何通过引用进行更新,而在另一种情况下,通过复制进行更新.在一般情况下,我们怎么知道会发生什么.
这在Hadley的高级R书中有所涉及.他说(在这里解释)每当2个或更多变量指向同一个对象时,R将复制然后修改该副本.在进入实例之前,哈德利的书中提到的一个重要注意事项是,当你使用时RStudio
环境浏览器引用您在命令行上创建的每个对象.
鉴于你观察到的行为,我假设你正在使用RStudio我们将会看到的将解释为什么实际上有两个变量指向a而不是你可能期望的1.
我们将用来检查指向对象的变量数量的函数是refs().在您发布的第一个示例中,您可以看到:
library(pryr)
a = 1:10
refs(x)
#[1] 2
Run Code Online (Sandbox Code Playgroud)
这表明(这是你发现的)2个变量所指向的a,因此任何修改a都会导致R复制它,然后修改该副本.
检查for loop我们可以看到y始终具有相同的地址和refs(y) = 1for循环中的地址.y未复制,因为y您的函数中没有其他引用指向y[i] = x[i] - x[i-1]:
for(i in 2:length(x))
{
y[i] = x[i] - x[i-1]
print(c(address(y), refs(y)))
}
#[1] "0x19c3a230" "1"
#[1] "0x19c3a230" "1"
#[1] "0x19c3a230" "1"
#[1] "0x19c3a230" "1"
#[1] "0x19c3a230" "1"
#[1] "0x19c3a230" "1"
#[1] "0x19c3a230" "1"
#[1] "0x19c3a230" "1"
#[1] "0x19c3a230" "1"
Run Code Online (Sandbox Code Playgroud)
另一方面,如果在你的内容中引入一个非原始函数,你会看到每次更改的地址,这更符合我们的预期:yfor loopy
is.primitive(lag)
#[1] FALSE
for(i in 2:length(x))
{
y[i] = lag(y)[i]
print(c(address(y), refs(y)))
}
#[1] "0x19b31600" "1"
#[1] "0x19b31948" "1"
#[1] "0x19b2f4a8" "1"
#[1] "0x19b2d2f8" "1"
#[1] "0x19b299d0" "1"
#[1] "0x19b1bf58" "1"
#[1] "0x19ae2370" "1"
#[1] "0x19a649e8" "1"
#[1] "0x198cccf0" "1"
Run Code Online (Sandbox Code Playgroud)
注意强调非原始的.如果你的功能y是原始如-:y[i] = y[i] - y[i-1]R可以优化这个以避免复制.
感谢@duckmayr帮助解释for循环中的行为.
我完成了@MikeH。awnser 与此代码
library(pryr)
x = runif(10)
y = numeric(length(x))
print(c(address(y), refs(y)))
for(i in 2:length(x))
{
y[i] = x[i] - x[i-1]
print(c(address(y), refs(y)))
}
print(c(address(y), refs(y)))
Run Code Online (Sandbox Code Playgroud)
输出清楚地显示发生了什么
[1] "0x7872180" "2"
[1] "0x765b860" "1"
[1] "0x765b860" "1"
[1] "0x765b860" "1"
[1] "0x765b860" "1"
[1] "0x765b860" "1"
[1] "0x765b860" "1"
[1] "0x765b860" "1"
[1] "0x765b860" "1"
[1] "0x765b860" "1"
[1] "0x765b860" "2"
Run Code Online (Sandbox Code Playgroud)
第一次迭代时有一个副本。事实上,因为 Rstudio 有 2 个参考。但此后第一个副本y属于循环并且不可用于全局环境。然后,Rstudio 不会创建任何额外的引用,因此在下次更新期间不会进行任何复制。y通过引用更新。循环退出y在全局环境中变得可用。Rstudio 创建了一个额外的引用,但此操作显然不会更改地址。