向量上的复制修改语义不会附加到循环中。为什么?

时间:2018-01-12 16:28:15

标签: r pass-by-reference pass-by-value

这个问题听起来部分回答here,但这对我来说还不够具体。通过引用更新对象以及复制对象时,我想更好地理解。

更简单的例子是矢量增长。以下代码在R中非常低效,因为在循环之前没有分配内存,并且在每次迭代时都进行了复制。

1,000,000.000

分配内存使能保留一些内存,而无需在每次迭代时重新分配内存。因此,这个代码的速度非常快,特别是对于长向量。

  x = runif(10)
  y = c() 

  for(i in 2:length(x))
    y = c(y, x[i] - x[i-1])

这是我的问题。实际上,当矢量更新时,移动。有一份副本如下所示。

  x = runif(10)
  y = numeric(length(x))

  for(i in 2:length(x))
    y[i] = x[i] - x[i-1]

但是在一个循环中,这个副本不会发生

a = 1:10
pryr::tracemem(a)
[1] "<0xf34a268>"
a[1] <- 0L
tracemem[0xf34a268 -> 0x4ab0c3f8]:
a[3] <-0L
tracemem[0x4ab0c3f8 -> 0xf2b0a48]:  

给出

y = numeric(length(x))
for(i in 2:length(x))
{
   y[i] = x[i] - x[i-1]
   print(address(y))
}

我理解为什么代码作为内存分配的函数是缓慢的还是快的但我不理解R逻辑。对于同一语句,为什么以及如何通过引用进行更新,而在另一种情况下,通过复制进行更新。在一般情况下,我们怎么知道会发生什么。

2 个答案:

答案 0 :(得分:8)

Hadley的高级R书中对此进行了介绍。他说(在这里解释)每当2个或更多变量指向同一个对象时,R将复制然后修改该副本。在进入示例之前,Hadley的书中还提到了一个重要的注释,即当你使用RStudio

  

环境浏览器引用您在命令行上创建的每个对象。

鉴于您观察到的行为,我假设您正在使用RStudio,我们将会看到这将解释为什么实际上有2个变量指向a而不是1,就像您期望的那样。

我们将用于检查指向对象的变量数量的函数是refs()。在您发布的第一个示例中,您可以看到:

library(pryr)
a = 1:10
refs(x)
#[1] 2

这表明(这是你发现的)2个变量指向a,因此对a的任何修改都会导致R复制它,然后修改该副本。

检查for loop我们可以看到y始终在for循环中具有相同的地址和refs(y) = 1。未复制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" 

另一方面,如果在y中引入for loop非原始函数,您会看到每次y的地址都会发生变化更符合我们的期望:

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"  

注意强调非原始。如果y的函数是原始的,例如-,则y[i] = y[i] - y[i-1] R可以优化此函数以避免复制。

感谢@duckmayr帮助解释for循环中的行为。

答案 1 :(得分:1)

我完成了@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)))

输出显示发生了什么

[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"  

第一次迭代时有一个副本。确实因为Rstudio有2个参考。但是,在第一个副本y属于循环后,并且不可用于全局环境。然后,Rstudio不会创建任何其他引用,因此在下次更新期间不会复制。 y通过引用更新。循环退出y在全局环境中可用。 Rstudio创建了一个额外的引用,但这个动作显然没有改变地址。