这似乎是fread
错误,但我不确定。
此示例重现了我的问题。我有一个函数,我在其中读取data.table并将其返回到列表中。我使用list将其他结果分组到相同的结构中。这是我的代码:
ff.fread <- function(){
dt = fread("x
1
2
")
list(dt=dt)
}
DT.f <- ff.fread()$dt
现在,当我尝试向DT.f添加新列时,它可以正常工作,但我收到一条警告消息:
DT.f[,y:=1:2]
Warning message:
In `[.data.table`(DT.f, , `:=`(y, 1:2)) :
Invalid .internal.selfref detected and fixed by taking a copy of the whole
table so that := can add this new column by reference. At an earlier point,
this data.table has been copied by R (or been created manually using
structure() or similar). Avoid key<-, names<- and attr<- which in R currently
(and oddly) may copy the whole data.table. Use set* syntax instead to avoid
copying: ?set, ?setnames and ?setattr. Also, in R<v3.1.0, list(DT1,DT2) copied
the entire DT1 and DT2 (R's list() used to copy named objects); please upgrade
to R>=v3.1.0 if that is biting. If this message doesn't help, please report to
datatable-help so the root cause can be fixed.
请注意,如果我手动创建data.table,我没有此警告。这很好,例如:
ff <- function(){
list(dt=data.table(x=1:2))
}
DT <- ff()$dt
DT[,y:=1:2]
或者,如果我没有在列表中返回fread
的结果,那么它也可以正常运行
ff.fread <- function(){
dt = fread("x
1
2
")
dt
}
答案 0 :(得分:24)
这与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版本&gt; = 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
&#39; s copy()
)的问题是R内部将truelength
参数设置为0甚至虽然truelength(.)
函数仍将返回正确的结果。这通过:=
引用更新时无意中导致段错误,因为超额分配不再存在(或者至少不存在)已经认出了)。这发生在版本&lt; 1.7.8。为了克服这个问题,引入了一个名为.internal.selfref
的属性。您可以通过执行attributes(DT)
来检查此属性。
来自NEWS(第1.7.8节):
o克里斯崩溃&#39;是固定的。根本原因是
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旨在处理对诸如 handle 之类的C结构的引用,并且例如在包RODBC中用于此目的。它们的复制语义不同寻常,因为复制R对象时,外部指针对象不会重复。
它们被创建为:
SEXP R_MakeExternalPtr(void *p, SEXP tag, SEXP prot);
其中p是指针(因此它不能作为函数指针),而tag和prot是对普通R对象的引用,它们将在外部指针对象的生命周期内保持存在(受到垃圾收集保护) 。一个有用的约定是将标记字段用于某种形式的类型标识,使用prot字段来保护外部指针所代表的内存,如果该内存是从R堆分配的。
在我们的例子中,我们为DT创建/的属性.internal.selfref
,其值是一个指向NULL的外部指针(你在属性值中看到的地址)和这个外部指针&#39; s { {1}}字段是另一个返回prot
的外部指针(因此称为 selfref ),此次DT
设置为NULL。
注意:我们已将此extptr用于NULL,其中&#39; prot&#39;是一个extptr策略,以便prot
是两个不同的副本,但具有相同的内容返回TRUE。 (如果你不明白这意味着什么,你可以跳到下一部分。它与理解这个问题的答案无关。)
我们知道在复制过程中外部指针不会重复。基本上,当我们创建data.table时,属性.internal.selfref会创建一个指向NULL的外部指针,并使用identical(DT1, DT2)
字段创建一个返回prot
的外部指针。现在,当一个无意的&#34;副本&#34;正在制作,对象的地址被修改但不受属性保护的地址。它仍然指向DT
是否存在......因为它不会被修改。因此,通过检查当前对象的地址和外部指针保护的地址,可以在内部检测到这一点。如果他们不匹配,那么&#34;复制&#34; R已经完成(可能会丢失过度分配的data.table精心创建)。那就是:
DT
要接受很多东西。我想我已经设法尽可能清楚地完成它。如果出现任何错误(我需要一段时间将其包裹在我的脑海中)或进一步明确的可能性,请随时编辑或评论您的建议。
希望这可以解决问题。
答案 1 :(得分:9)
list()
的具体特征是它复制命名输入(在调用list()
之前已经命名的事物)。现在在r-devel(R的下一个版本)中,list()
的副本不再发生,一切都会很好。这是R中非常受欢迎的变化。
与此同时,您可以通过以不同方式创建输出列表来解决此问题。
> R.version.string
[1] "R version 3.0.2 (2013-09-25)"
首先演示list()复制:
> DT = data.table(a=1:3)
> address(DT)
[1] "0x1d70010"
> address(list(DT)[[1]])
[1] "0x21bc178" # different address => list() copied the data.table named DT
> data.table:::selfrefok(DT)
[1] 1
> data.table:::selfrefok(list(DT)[[1]])
[1] 0 # i.e. this copied DT is not over-allocated
现在以不同的方式创建相同的列表:
> ans = list()
> ans$DT = DT # use $<- instead
> address(DT)
[1] "0x1d70010"
> address(ans$DT)
[1] "0x1d70010" # good, no copy
> identical(ans, list(DT=DT))
[1] TRUE
> data.table:::selfrefok(ans$DT)
[1] 1 # good, the list()-ed DT is still over-allocated ok
我知道,令人困惑和困惑。使用$<-
创建输出列表,甚至只是将fread
的调用放在list()
list(DT=fread(...))
的调用中,应该避免list()
的副本。