为什么要在 R 中复制列表?

Why is list being copied in R?

我最近一直在尝试通过阅读和试验 R 中的内存使用来提高我的 R 编程技能。我最近试图在 Hadley 的 "Advanced R," 中重新创建一个示例,但得到了不同的结果。在 this page 的底部,Hadley 设置了以下示例:

x <- data.frame(matrix(runif(100 * 1e4), ncol = 100))
medians <- vapply(x, median, numeric(1))

然后通过展示

来展示原始和非原始之间的区别
for(i in 1:5) {
  x[, i] <- x[, i] - medians[i]
  print(c(address(x), refs(x)))
}

每次循环运行时都制作一个副本(因为 x 是一个数据框)。而

y <- as.list(x)

for(i in 1:5) {
  y[[i]] <- y[[i]] - medians[i]
  print(c(address(y), refs(y)))
}

就地修改 y(因为 y 已经转换为列表,这是一个原始对象)。然而,当我重新创建这段代码时,我发现在这两种情况下都制作了副本:

> x <- data.frame(matrix(runif(100 * 1e4), ncol = 100))
> medians <- vapply(x, median, numeric(1))
> 
> for(i in 1:5) {
+   x[, i] <- x[, i] - medians[i]
+   print(c(address(x), refs(x)))
+ }
[1] "0x10e4e6770" "2"          
[1] "0x10e46c420" "2"          
[1] "0x121110180" "2"          
[1] "0x11c2c26d0" "2"          
[1] "0x121151db0" "2"  


> x <- data.frame(matrix(runif(100 * 1e4), ncol = 100))
> medians <- vapply(x, median, numeric(1))
> y <- as.list(x)
> 
> for(i in 1:5) {
+   y[[i]] <- y[[i]] - medians[i]
+   print(c(address(y), refs(y)))
+ }
[1] "0x132aea2b0" "2"          
[1] "0x1211839e0" "2"          
[1] "0x11c237ea0" "2"          
[1] "0x121169a80" "2"          
[1] "0x10993f460" "2" 

Hadley 在他的示例中似乎至少使用了 R 3.1.0,而我使用的是 R 3.1.2(在 Mac 上)。然而,我读过的所有内容都表明,随着时间的推移,R 在内存管理方面变得越来越好,而上述结果表明它正在变得更糟。尽管我可能会做一些愚蠢的事情或误解一些重要的事情。谁能告诉我为什么我的复制品没有与 Hadley 的例子相同的内存效率?

这不是您问题的直接答案,但我认为您的脚本可能会误导您。列表地址的变化data.frame并不意味着所有数据的复制。

R中的

listdata.frame是R对象的向量。在您的脚本中,R 应该只复制替换的 R 对象,因此列表的地址已更改。

例如:

x <- data.frame(matrix(runif(100 * 1e4), ncol = 100))
medians <- vapply(x, median, numeric(1))

for(i in 1:5) {
  print(sprintf("===%d===", i))
  for(j in 1:5) print(sprintf("%s(%d)", address(x[[j]]), j))
  x[, i] <- x[, i] - medians[i]
  print(c(address(x), refs(x)))
  for(j in 1:5) print(sprintf("%s(%d)", address(x[[j]]), j))
}

y <- as.list(x)

for(i in 1:5) {
  print(sprintf("===%d===", i))
  for(j in 1:5) print(sprintf("%s(%d)", address(y[[j]]), j))
  y[[i]] <- y[[i]] - medians[i]
  for(j in 1:5) print(sprintf("%s(%d)", address(y[[j]]), j))
}

您应该看到每次迭代中只有 1 个地址发生变化。赋值<-只复制对应R对象的数据,即x[[i]]y[[i]]。左边的 4 个对象没有被复制。

正如目前在 pryr 的 CRAN 版本中实现的那样,当 运行 在当前 R 中时,address() 函数会将其参数的引用数量增加到 2。一旦发生这种情况,就必须复制对象更换电话。 refs() 函数实现避免添加引用,所以如果你只是打印 refs(y) 你会看到它保持在 1 并且不会有重复。