这与fread 本身无关,但您调用list() 并将其传递给命名对象。我们可以通过这样做来重新创建它:
require(data.table)
DT <- data.table(x=1:2) # name the object 'DT'
DT.l <- list(DT=DT) # create a list containing one data.table
y <- DT.l$DT # get back the data.table
y[, bla := 1L] # now add by reference
# works fine but warning message will occur
DT.l = list(DT=data.table(x=1:2)) # DT = a call, not a named object
y = DT.l$DT
y[, bla:=1L]
# works fine and no warning message
好消息:
好消息是,从 R 版本 >= 3.1.0(现在处于开发阶段)开始,将命名对象传递给 list() 将不再创建副本,而是创建其引用计数 (指向该值的对象数量)只是被碰撞了。所以,这个问题随着 R 的下一个版本消失了。
要了解data.table 如何使用.internal.selfref 检测副本,我们将深入了解data.table 的一些历史。
首先,一些历史:
您应该知道data.table 在创建时会过度分配列指针槽(truelength 设置为默认值 100),以便稍后可以使用:= 通过引用添加列。这样有一个问题 - 处理副本。例如,当我们调用list() 并向其传递一个命名对象时,正在制作一个副本,如下所示。
tracemem(DT)
# [1] "<0x7fe23ac3e6d0>"
DT.list <- list(DT=DT) # `DT` is the named object on the RHS of = here
# tracemem[0x7fe23ac3e6d0 -> 0x7fe23cd72f48]:
R 制作的任何data.table 副本(不是data.table 的copy())的问题是R 在内部将truelength 参数设置为0,即使truelength(.) 函数仍将返回正确的结果.这在使用 := 引用更新时无意中导致了 segfault,因为 over-allocation 不再存在(或者至少不再被识别)。这发生在版本 .internal.selfref 的属性。你可以通过attributes(DT)来检查这个属性。
来自新闻(v1.7.8):
o“克里斯崩溃”已修复。根本原因是key<- 总是复制整个表。 该副本的问题(除了速度较慢)是 R 没有维护过度分配的 truelength,但它看起来好像有。 key<- 在内部使用,特别是在 merge() 中。因此,在merge() 之后使用:= 添加一列是内存覆盖,因为在key<- 的副本之后,过度分配的内存实际上并不存在。
data.tables 现在有一个新属性.internal.selfref 可以在将来捕获和警告此类副本。 key<- 的所有内部使用都已替换为setkey(),或接受向量且不复制的新函数setkeyv()。
.internal.selfref 是做什么的?
基本上,它只是指向自己。它只是附加到DT 的一个属性,它包含DT 在RAM 中的地址。如果 R 无意中复制了DT,DT 的地址将在 RAM 中移动,但附加的属性仍将包含旧的内存地址,它们将不再匹配。 data.table 在通过引用将新列添加到备用列指针槽之前检查它们是否匹配(即有效)。
.internal.selfref是如何实现的?
为了理解.internal.selfref 这个属性,我们必须了解外部指针 (EXTPTRSXP) 是什么。 This page 解释得很好。复制/粘贴基本行:
外部指针 SEXP 旨在处理对 C 结构(例如 handles)的引用,例如在 RODBC 包中用于此目的。它们的复制语义不同寻常,因为当复制 R 对象时,外部指针对象不会被复制。
它们被创建为:
SEXP R_MakeExternalPtr(void *p, SEXP tag, SEXP prot);
其中 p 是指针(因此不能移植为函数指针),并且 tag 和 prot 是对普通 R 对象的引用,这些对象将在外部指针对象的生命周期内保持存在(防止垃圾收集) .一个有用的约定是使用 tag 字段进行某种形式的类型标识,并使用 prot 字段来保护外部指针表示的内存,如果该内存是从 R 堆分配的。
在我们的例子中,我们为DT创建了属性.internal.selfref,它的值是一个指向NULL的外部指针(你在属性值中看到的地址),这个外部指针的prot字段是另一个外部指向DT(因此称为selfref)的指针,这次将其prot设置为NULL。
注意:我们必须将此 extptr 用于 NULL,其 'prot' 是一个 extptr 策略,以便 identical(DT1, DT2) 是两个不同的副本,但具有相同的内容返回 TRUE。 (如果你不明白这意味着什么,你可以直接跳到下一部分。这与理解这个问题的答案无关)。
好的,那么这一切是如何工作的呢?
我们知道外部指针不会在复制过程中重复。基本上,当我们创建一个 data.table 时,属性 .internal.selfref 创建一个指向 NULL 的外部指针,它的 prot 字段创建一个指向 DT 的外部指针。现在,当无意“复制”时,对象的地址被修改,但不是受属性保护的地址。它仍然指向DT 是否存在.. 因为它不会/不能被修改。因此,这是通过检查当前对象的地址和受外部指针保护的地址在内部检测到的。如果它们不匹配,则 R 制作了一个“副本”(这将丢失 data.table 精心创建的过度分配)。那就是:
DT <- data.table(x=1:2) # internal selfref set
DT.list <- list(DT=DT) # copy made, address(DT.list$DT) != address(DT)
# and truelength would be affected.
DT.new <- DT.list$DT # address of DT.new != address of DT
# and it's not equal to the address pointed to by
# the attribute's 'prot' external pointer
# so a re-over-allocation has to be made by data.table at the next update by
# reference, and it warns so you can fix the root cause by not using list(),
# key<-, names<- etc.
要考虑的内容很多。我想我已经尽可能清楚地完成了。如果有任何错误(我花了一段时间才把它绕在我的脑海里)或有进一步澄清的可能性,请随时编辑或评论您的建议。
希望这能解决问题。