考虑这个变量
a = data.frame(x=1:5,y=2:6)
当我使用替换函数更改
a
的第一个元素时,需要进行几次是否复制了与
a
相同大小的内存?tracemem(a)
"change_first_element<-" = function(x, value) {
x[1,1] = value
return(x)
}
change_first_element(a) = 3
# tracemem[0x7f86028f12d8 -> 0x7f86028f1498]:
# tracemem[0x7f86028f1498 -> 0x7f86028f1508]: change_first_element<-
# tracemem[0x7f86028f1508 -> 0x7f8605762678]: [<-.data.frame [<- change_first_element<-
# tracemem[0x7f8605762678 -> 0x7f8605762720]: [<-.data.frame [<- change_first_element<-
有四个复制操作。我知道R不会变异对象或通过引用传递(是的,有异常(exception)),但是为什么会有四个副本呢?一份副本不够吗?
第2部分:
如果我以不同的方式调用替换函数,那么只有三个复制操作?
tracemem(a)
a = `change_first_element<-`(a,3)
# tracemem[0x7f8611f1d9f0 -> 0x7f8607327640]: change_first_element<-
# tracemem[0x7f8607327640 -> 0x7f8607327758]: [<-.data.frame [<- change_first_element<-
# tracemem[0x7f8607327758 -> 0x7f8607327800]: [<-.data.frame [<- change_first_element<-
最佳答案
注意:除非另有说明,否则以下所有说明对R版本要回答您的第一个问题,“为什么要复制四份,还不够?”,我们首先从R-internals引用相关部分开始:
NAM(1):
让我们从NAM(1)
对象开始。这是一个例子:
x <- 1:5 # (1)
.Internal(inspect(x))
# @10374ecc8 13 INTSXP g0c3 [NAM(1)] (len=5, tl=0) 1,2,3,4,5
tracemem(x)
# [1] "<0x10374ecc8>"
x[2L] <- 10L # (2)
.Internal(inspect(x))
# @10374ecc8 13 INTSXP g0c3 [MARK,NAM(1),TR] (len=5, tl=0) 1,10,3,4,5
这里发生了什么事?我们使用:
创建了一个整数 vector ,它是一个原始图元,产生了NAM(1)对象。当我们在该对象上使用[<-
时,该值就地更改了(请注意,指针是相同的(1)和(2))。这是因为[<-
是一种原语,非常了解如何处理其输入,并且在这种情况下针对无副本进行了优化。y = x # (3)
.Internal(inspect(x))
# @10374ecc8 13 INTSXP g0c3 [MARK,NAM(2),TR] (len=5, tl=0) 1,10,3,4,5
x[2L] <- 20L # (4)
.Internal(inspect(x))
# tracemem[0x10374ecc8 -> 0x10372f328]:
# @10372f328 13 INTSXP g0c3 [NAM(1),TR] (len=5, tl=0) 1,20,3,4,5
现在,相同的作业会产生一个副本,为什么?通过执行(3),当多个对象指向同一数据时,“命名”字段将递增为NAM(2)。即使对[<-
进行了优化,它也是NAM(2)
的事实也意味着必须复制对象。这就是为什么现在在分配后再次成为NAM(1)
对象的原因。这是因为,调用duplicate
会将named
设置为0,而新的赋值会将其重新设置为1。NAM(2):
现在,让我们回到有关在作为
*<-
对象的data.frame
上调用NAM(2)
的问题。那么第一个问题是,为什么
data.frame()
是NAM(2)
对象?为什么不像以前的 x <- 1:5
这样的NAM(1)? Duncan Murdoch在same post上很好地回答了这个问题:这意味着任何更改该值的尝试都会导致触发
duplicate
(深拷贝)。从?tracemem
:因此,来自
tracemem
的消息有助于了解副本数。为了理解tracemem
输出的第一行,让我们构造函数f<-
,它没有实际替代。另外,让我们构造一个足够大的data.frame
,以便我们可以测量该data.frame
的单个副本所花费的时间。## R v 3.0.3
`f<-` = function(x, value) {
return(x) ## no actual replacement
}
df <- data.frame(x=1:1e8, y=1:1e8) # 762.9 Mb
tracemem(df) # [1] "<0x7fbccd2f4ae8>"
require(data.table)
system.time(copy(df))
# tracemem[0x7fbccd2f4ae8 -> 0x7fbccd2f4ff0]: copy system.time
# user system elapsed
# 0.609 0.484 1.106
system.time(f(df) <- 3)
# tracemem[0x7fbccd2f4ae8 -> 0x7fbccd2f4f10]: system.time
# user system elapsed
# 0.608 0.480 1.101
我已经使用了copy()
中的data.table
函数(基本上称为C duplicate
函数)。复制的时间大致相同。因此,第一步显然是深层复制,即使它什么也不做。这说明了帖子中
tracemem
的前两个详细消息:使用
debugonce()
上的[<-.data.frame
可以轻松找到其余副本。就是说:debugonce(`[<-`)
df <- data.frame(x=1:1e8, y=1:1e8)
`f<-` = function(x, value) {
x[1,1] = value
return(x)
}
tracemem(df)
f(df) = 3
# first three lines:
# tracemem[0x7f8ba33d8a08 -> 0x7f8ba33d8d50]: (1)
# tracemem[0x7f8ba33d8d50 -> 0x7f8ba33d8a78]: f<- (2)
# debugging in: `[<-.data.frame`(`*tmp*`, 1L, 1L, value = 3L)
通过按回车键,您将发现其他两个副本位于此函数内:# debug: class(x) <- NULL
# tracemem[0x7f8ba33d8a78 -> 0x7f8ba3cd6078]: [<-.data.frame [<- f<- (3)
# debug: x[[jj]][iseq] <- vjj
# tracemem[0x7f8ba3cd6078 -> 0x7f882c35ed40]: [<-.data.frame [<- f<- (4)
注意class
是原始的,但是正在NAM(2)对象上调用它。我怀疑这就是在那里复制的原因。最后的副本是不可避免的,因为它修改了该列。所以,你去了。
现在关于
R v3.1.0
的小注释:在您的第二部分,我将引用here的Luke Tierney:
但是我无法确定这些不愉快的惊喜是否会扩展到已经是
NAM(2)
的对象。因为,Matt是在list
上调用它的,它是一个原始值,因此是NAM(1),直接调用foo<-
不会增加它的“命名”值。但是,R v3.1.0有了很大的改进,这一事实应该已经使您确信不再需要这样的函数调用。
HTH。
PS:请随时纠正我(如果可能,请帮助我缩短答案):)。
编辑:我似乎错过了直接在注释下发现调用
f<-
时副本减少的问题。使用帖子中使用的Simon Simonek函数(现在已多次链接)很容易看到:# rm(list=ls()) # to make sure there' no other object in your workspace
`f<-` <- function(x, value) {
print(ls(env = parent.frame()))
}
df <- data.frame(x=1, y=2)
tracemem(df) # [1] "<0x7fce01a65358>"
f(df) = 3
# tracemem[0x7fce0359b2a0 -> 0x7fce0359ae08]:
# [1] "*tmp*" "df" "f<-"
df <- data.frame(x=1, y=2)
tracemem(df) # [1] "<0x7fce03c505c0>"
df <- `f<-`(df, 3)
# [1] "df" "f<-"
如您所见,在第一种方法中,正在创建一个对象*tmp*
,在第二种情况中则没有。看起来,为*tmp*
输入对象创建NAM(2)
对象会在*tmp*
分配给函数参数之前触发输入的副本。但是据我所知。