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

2023-11-23

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

更简单的例子是向量增长。下面的代码在 R 中效率极低,因为在循环之前没有分配内存,并且在每次迭代时都会创建一个副本。

  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]

我的问题来了。实际上当向量更新时does移动。有一个副本,如下所示。

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))
}

Gives

[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0" 

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


Hadley 的《Advanced R》一书中对此进行了介绍。在其中他说(这里解释一下)每当两个或多个变量指向同一个对象时,R 将创建一个副本,然后修改该副本。在进入示例之前,哈德利的书中也提到了一个重要的注意事项,即当您使用RStudio

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

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

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

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

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

检查for loop我们可以看到y总是有相同的地址refs(y) = 1在 for 循环中。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是原始的,例如- like: y[i] = y[i] - y[i-1]R 可以对此进行优化以避免复制。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

向量上的修改时复制语义不会在循环中追加。为什么? 的相关文章

随机推荐