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

 nicknick-AUG 发布于 2023-02-09 10:33

这似乎是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.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);

其中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.

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

希望这可以解决问题.

2 个回答
  • 这与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.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);
    

    其中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.
    

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

    希望这可以解决问题.

    2023-02-09 10:35 回答
  • 阿伦的答案是一个很好的解释.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().

    2023-02-09 10:37 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有