是的,在R中使用<-
(或=
或->
)的子分配可以复制whole对象.你可以用tracemem(DT)
和.Internal(inspect(DT))
来追踪,如下所示.data.table
特征:=
和set()
通过引用分配给它们传递的任何对象.因此,如果该对象以前被复制过(通过子授权<-
或显式copy(DT)
),那么它就是通过引用修改的副本.
DT <- data.table(a = c(1, 2), b = c(11, 12))
newDT <- DT
.Internal(inspect(DT))
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12
# ATTRIB: # ..snip..
.Internal(inspect(newDT)) # precisely the same object at this point
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12
# ATTRIB: # ..snip..
tracemem(newDT)
# [1] "<0x0000000003b7e2a0"
newDT$b[2] <- 200
# tracemem[0000000003B7E2A0 -> 00000000040ED948]:
# tracemem[00000000040ED948 -> 00000000040ED830]: .Call copy $<-.data.table $<-
.Internal(inspect(DT))
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),TR,ATT] (len=2, tl=100)
# @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12
# ATTRIB: # ..snip..
.Internal(inspect(newDT))
# @0000000003D97A58 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040ED7F8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040ED8D8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,200
# ATTRIB: # ..snip..
请注意,即使a
没有更改,a
向量也是如何被复制的(不同的十六进制值表示向量的新副本).甚至整个b
都被复制,而不仅仅是改变需要改变的元素.这对于 Big Data 来说很重要,也是为什么:=
和set()
被引入data.table
的原因.
现在,使用我们复制的newDT
,我们可以通过引用对其进行修改:
newDT
# a b
# [1,] 1 11
# [2,] 2 200
newDT[2, b := 400]
# a b # See FAQ 2.21 for why this prints newDT
# [1,] 1 11
# [2,] 2 400
.Internal(inspect(newDT))
# @0000000003D97A58 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040ED7F8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040ED8D8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,400
# ATTRIB: # ..snip ..
请注意,所有3个十六进制值(列点的向量和2列中的每一列)保持不变.所以它确实是通过引用修改的,根本没有拷贝.
或者,我们可以参考修改原来的DT
:
DT[2, b := 600]
# a b
# [1,] 1 11
# [2,] 2 600
.Internal(inspect(DT))
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,600
# ATTRIB: # ..snip..
这些十六进制值与我们在上面看到的DT
的原始值相同.键入example(copy)
以获取更多示例,使用tracemem
并与data.frame
进行比较.
顺便说一句,如果你tracemem(DT)
然后DT[2,b:=600]
你会看到一份报告.方法的第一个副本是10行.当用invisible()
包装或在函数或脚本中调用时,不会调用print
方法.
所有这些都适用于内部函数;i、 例如,:=
和set()
即使在函数中也不会进行写时复制.如果需要修改本地副本,则在函数开始时调用x=copy(x)
.但是,请记住data.table
适用于 Big Data (以及小数据的更快编程优势).我们故意不想复制大对象(永远).因此,我们不需要考虑通常的3*工作记忆系数经验法则.我们尽量只需要一列的工作记忆(即工作记忆系数为1/ncol而不是3).