避免在R中复制大对象-get()函数是否进行复制?

时间:2019-05-30 16:14:50

标签: r

我继承了一些R语言代码,该语言使用了一种惯用法来避免复制大数据帧。当构建数据框架的函数完成时,它不返回数据框架,而是将其存储在全局环境上下文中:

assign(df_name, df, envir = globalenv())

函数完成后,调用代码使用以下方法检索数据帧:

df <- get(df_name, envir = globalenv())

我的问题是这样的:get()函数本身在返回值时实际上是否会创建副本,从而创建了该惯用法应避免的副本?如果是这样,还有更好的方法吗?

3 个答案:

答案 0 :(得分:3)

总而言之,这是胡说八道。在这里制作副本的是两个 分配(通过assign<-进行分配)。仅从该函数返回对象就可以保存其中一份副本。

偶然地,复制本身是 cheap ,因为R实现了一个称为“ copy on write”语义的概念:上面的命令在逻辑上进行复制,但是在物理上仅增加了内部的引用计数器复制的参考。仅当您通过参考之一修改数据时,才会复制参考后面的实际数据。

答案 1 :(得分:3)

据我所知,返回内容实际上并不会将其从一个内存位置复制到另一个内存位置。 几个测试:

trace_return <- function() {
  df <- data.frame(a = 1:10, b = letters[1:10])
  print(tracemem(df))
  df
}

ans <- trace_return()
# "<00000188C114D578>"
ans$a[1L] <- 0L # copy triggered on modify
# tracemem[0x00000188c114d578 -> 0x00000188c10ab098]: 
# tracemem[0x00000188c10ab098 -> 0x00000188c10ab258]: $<-.data.frame $<- 
# tracemem[0x00000188c10ab258 -> 0x00000188c10ab358]: $<-.data.frame $<-

并且:

e <- new.env()
e$ans <- trace_return()
# "<00000188C13F8EF8>"
ans <- e$ans # no copy here
ans$b <- NULL
# tracemem[0x00000188c13f8ef8 -> 0x00000188c14293f8]: 
# tracemem[0x00000188c14293f8 -> 0x00000188c1429378]: $<-.data.frame $<- 
# tracemem[0x00000188c1429378 -> 0x00000188c1429278]: $<-.data.frame $<- 

答案 2 :(得分:0)

是否存在相同data.frame的两个副本并不重要。但老实说,我看不出要两次分配data.frame的任何意义。

您可以使用pryr包查看对象分配了多少内存。

library(pryr)

df <- do.call(data.frame, replicate(8000, rep(FALSE, 8000), simplify=FALSE))

assign("dname_out", df, envir = globalenv())

mem_used()
337 MB

mem_change(df <- get("dname_out", envir = globalenv()))

736 B

> mem_used()
337 MB

内存变化仅为736字节,因此实际上您不会因创建大量副本而使PC崩溃。