警告:在向函数返回的data.table中添加列时,"检测到无效的.internal.selfref"

ags*_*udy 12 r data.table

这似乎是fread错误,但我不确定.

这个例子重现了我的问题.我有一个函数,我在其中读取data.table并将其返回到列表中.我使用list将其他结果分组到相同的结构中.这是我的代码:

ff.fread <- function(){
  dt = fread("x
1
2
")
  list(dt=dt)   
}

DT.f <- ff.fread()$dt
Run Code Online (Sandbox Code Playgroud)

现在,当我尝试向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.
Run Code Online (Sandbox Code Playgroud)

请注意,如果我手动创建data.table我没有此警告.这工作正常,例如:

ff <- function(){
      list(dt=data.table(x=1:2))
    }
DT <- ff()$dt
DT[,y:=1:2]
Run Code Online (Sandbox Code Playgroud)

或者如果我不返回fread列表中的结果,它也可以正常工作

ff.fread <- function(){
  dt = fread("x
1
2
")
  dt
}
Run Code Online (Sandbox Code Playgroud)

Aru*_*run 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
Run Code Online (Sandbox Code Playgroud)

好消息:

好消息是,从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]: 
Run Code Online (Sandbox Code Playgroud)

与任何副本的问题data.table是R品牌(没有data.tablecopy())条件是,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);
Run Code Online (Sandbox Code Playgroud)

其中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.
Run Code Online (Sandbox Code Playgroud)

这需要很多东西.我想我已经设法尽可能清楚地完成它.如果有任何错误(我需要一段时间将其包裹在我的脑海中)或进一步清晰的可能性,请随时编辑或评论您的建议.

希望这可以解决问题.

  • Arun非常感谢这个伟大的答案!+10如果可以的话! (2认同)

Mat*_*wle 10

阿伦的答案是一个很好的解释.list()R <= 3.0.2 的特定特征是它复制了命名输入(在调用之前已经命名的东西list()).现在r-devel(R的下一个版本),这个副本list()不再发生,一切都会很好.这是R中非常受欢迎的变化.

在此期间,您可以通过以不同方式创建输出列表来解决此问题.

> R.version.string
[1] "R version 3.0.2 (2013-09-25)"
Run Code Online (Sandbox Code Playgroud)

首先演示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
Run Code Online (Sandbox Code Playgroud)

现在以不同的方式创建相同的列表:

> 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
Run Code Online (Sandbox Code Playgroud)

我知道,令人困惑和困惑.利用$<-创建输出列表,甚至只是将调用fread内部调用list(),即list(DT=fread(...))应通过避免拷贝list().