警告:将列添加到从函数返回的 data.table 时“检测到无效 .internal.selfref”

Posted

技术标签:

【中文标题】警告:将列添加到从函数返回的 data.table 时“检测到无效 .internal.selfref”【英文标题】:Warning: 'Invalid .internal.selfref detected' when adding a column to a data.table returned from a function 【发布时间】:2014-01-08 08:21:46 【问题描述】:

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

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

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

【问题讨论】:

这与fread无关,与list有关,这里有一个更简单的例子:dt = data.table(a = 1); l = list(dt); dt1 = l[[1]]; dt1[, b := 2] 请注意,警告消息确实表明list() 复制了命名输入。现在已编辑完整的警告消息。 【参考方案1】:

这与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.tablecopy())的问题是R 在内部将truelength 参数设置为0,即使truelength(.) 函数仍将返回正确的结果.这在使用 := 引用更新时无意中导致了 segfault,因为 over-allocation 不再存在(或者至少不再被识别)。这发生在版本 .internal.selfref 的属性。你可以通过attributes(DT)来检查这个属性。

来自新闻(v1.7.8):

o“克里斯崩溃”已修复。根本原因是key&lt;- 总是复制整个表。 该副本的问题(除了速度较慢)是 R 没有维护过度分配的 truelength,但它看起来好像有key&lt;- 在内部使用,特别是在 merge() 中。因此,在merge() 之后使用:= 添加一列是内存覆盖,因为在key&lt;- 的副本之后,过度分配的内存实际上并不存在。

data.tables 现在有一个新属性.internal.selfref 可以在将来捕获和警告此类副本。 key&lt;- 的所有内部使用都已替换为setkey(),或接受向量且不复制的新函数setkeyv()

.internal.selfref 是做什么的?

基本上,它只是指向自己。它只是附加到DT 的一个属性,它包含DT 在RAM 中的地址。如果 R 无意中复制了DTDT 的地址将在 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.

要考虑的内容很多。我想我已经尽可能清楚地完成了。如果有任何错误(我花了一段时间才把它绕在我的脑海里)或有进一步澄清的可能性,请随时编辑或评论您的建议。

希望这能解决问题。

【讨论】:

警告消息包含以下内容:`另外,在 R 你是对的。我已经删除了参考。到tag (虽然它只占很小的一部分)。我认为这足以理解这个想法。 我认为警告指的是一种常见的情况。但只要发生了复制(不是通过实际的copy 函数),或者无论如何是无意的,那么必须正确复制:= 以便通过引用进行更新(因为它的truelength 将设置为0)。 .. 最后一件事,在您的示例中,如果我删除属性 setattr(y,'.internal.selfref',NULL) ,我仍然会收到警告。我想这个属性并没有真正被隐藏,只是被隐藏了.. Arun 非常感谢这个很棒的答案!如果可以的话+10!【参考方案2】:

Arun 的回答是一个很好的解释。 R 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

我知道这令人费解且令人困惑。使用$&lt;- 创建输出列表,甚至只是将fread 的调用放在list() 的调用中,即list(DT=fread(...)) 应该避免list() 的复制。

【讨论】:

以上是关于警告:将列添加到从函数返回的 data.table 时“检测到无效 .internal.selfref”的主要内容,如果未能解决你的问题,请参考以下文章

在data.table中逐行应用函数;将列名称作为向量传递

将列并行分配给 data.table

JSX 元素类型错误:将类型添加到从 .map 函数返回的对象

使用 R 中的多参数用户定义函数将列添加到数据框中

当data.table:=是最后一个操作时,R函数返回而不返回data.table对象[duplicate]

R从函数返回多个data.tables