这似乎是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 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 }
Arun.. 25
这与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(现在在devel中),传递命名对象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]:
与任何副本的问题data.table
是R品牌(没有data.table
的copy()
)条件是,R内部设置的truelength
参数为0,即使truelength(.)
函数仍然会返回正确的结果.这无意中导致了段错误时参照更新:=
,因为在过度分配不存在了(或者至少不再被认可).这发生在<1.7.8版本中.为了克服这个问题,.internal.selfref
引入了一个名为的属性.您可以通过执行来检查此属性attributes(DT)
.
来自NEWS(第1.7.8节):
o'克里斯崩溃'是固定的.根本原因是
key<-
始终复制整个表格.该副本的问题(除了较慢)是R不保持过度分配truelength
,但它看起来好像有.key<-
在内部使用,特别是在内部使用merge()
.因此,使用:=
after 添加列merge()
是内存覆盖,因为过度分配的内存在key<-
复制之后并不存在.
data.tables
现在有一个新的属性.internal.selfref
来捕捉和警告这些副本将来.所有内部使用key<-
已被替换为接受矢量的setkey()
新功能setkeyv()
,并且不会复制.
.internal.selfref
做什么的?它只是指向自己,基本上.它只是附加到的属性DT
,包含RAM中的地址DT
.如果R无意中复制DT
,则地址DT
将在RAM中移动但附加的属性仍将包含旧的内存地址,它们将不再匹配.data.table
在通过引用将新列添加到备用列指针槽之前,检查它们是否匹配(即有效).
.internal.selfref
实施?为了理解这个属性.internal.selfref
,我们要理解外部指针(EXTPTRSXP
)是什么.这个页面很好地解释了.复制/粘贴基本行:
外部指针SEXP旨在处理对诸如句柄之类的 C结构的引用,并且例如在包RODBC中用于此目的.它们的复制语义不同寻常,因为复制R对象时,外部指针对象不会重复.
它们被创建为:
SEXP R_MakeExternalPtr(void *p, SEXP tag, SEXP prot);
其中p是指针(因此它不能作为函数指针),而tag和prot是对普通R对象的引用,它们将在外部指针对象的生命周期内保持存在(受到垃圾收集保护).一个有用的约定是将标记字段用于某种形式的类型标识,使用prot字段来保护外部指针所代表的内存,如果该内存是从R堆分配的话.
在我们的例子中,我们.internal.selfref
为DT 创建了/ 的属性,其值是一个指向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.
这需要很多东西.我想我已经设法尽可能清楚地完成它.如果有任何错误(我需要一段时间将其包裹在我的脑海中)或进一步清晰的可能性,请随时编辑或评论您的建议.
希望这可以解决问题.
这与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(现在在devel中),传递命名对象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]:
与任何副本的问题data.table
是R品牌(没有data.table
的copy()
)条件是,R内部设置的truelength
参数为0,即使truelength(.)
函数仍然会返回正确的结果.这无意中导致了段错误时参照更新:=
,因为在过度分配不存在了(或者至少不再被认可).这发生在<1.7.8版本中.为了克服这个问题,.internal.selfref
引入了一个名为的属性.您可以通过执行来检查此属性attributes(DT)
.
来自NEWS(第1.7.8节):
o'克里斯崩溃'是固定的.根本原因是
key<-
始终复制整个表格.该副本的问题(除了较慢)是R不保持过度分配truelength
,但它看起来好像有.key<-
在内部使用,特别是在内部使用merge()
.因此,使用:=
after 添加列merge()
是内存覆盖,因为过度分配的内存在key<-
复制之后并不存在.
data.tables
现在有一个新的属性.internal.selfref
来捕捉和警告这些副本将来.所有内部使用key<-
已被替换为接受矢量的setkey()
新功能setkeyv()
,并且不会复制.
.internal.selfref
做什么的?它只是指向自己,基本上.它只是附加到的属性DT
,包含RAM中的地址DT
.如果R无意中复制DT
,则地址DT
将在RAM中移动但附加的属性仍将包含旧的内存地址,它们将不再匹配.data.table
在通过引用将新列添加到备用列指针槽之前,检查它们是否匹配(即有效).
.internal.selfref
实施?为了理解这个属性.internal.selfref
,我们要理解外部指针(EXTPTRSXP
)是什么.这个页面很好地解释了.复制/粘贴基本行:
外部指针SEXP旨在处理对诸如句柄之类的 C结构的引用,并且例如在包RODBC中用于此目的.它们的复制语义不同寻常,因为复制R对象时,外部指针对象不会重复.
它们被创建为:
SEXP R_MakeExternalPtr(void *p, SEXP tag, SEXP prot);
其中p是指针(因此它不能作为函数指针),而tag和prot是对普通R对象的引用,它们将在外部指针对象的生命周期内保持存在(受到垃圾收集保护).一个有用的约定是将标记字段用于某种形式的类型标识,使用prot字段来保护外部指针所代表的内存,如果该内存是从R堆分配的话.
在我们的例子中,我们.internal.selfref
为DT 创建了/ 的属性,其值是一个指向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.
这需要很多东西.我想我已经设法尽可能清楚地完成它.如果有任何错误(我需要一段时间将其包裹在我的脑海中)或进一步清晰的可能性,请随时编辑或评论您的建议.
希望这可以解决问题.
阿伦的答案是一个很好的解释.list()
R <= 3.0.2 的特定特征是它复制了命名输入(在调用之前已经命名的东西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()
.