Data.table: 内存泄漏

创建于 2018-05-11  ·  36评论  ·  资料来源: Rdatatable/data.table

只有在使用当前的 3.6.0 每晚构建时,我才发现 data.table 中似乎存在内存泄漏

# Minimal reproducible example

require(data.table)
n <- 2e6
df <- data.frame(a=rnorm(n),
                 b=factor(rbinom(n,5,prob=0.5),1:5,letters[1:5]),
                 c=factor(rbinom(n,5,prob=0.5),1:5,letters[1:5]))
dt <- setDT(df)
print(pryr::mem_used())
fff <- function(aref) {
  ff <- lapply(1:5, function(i) {
    dt2 <- dt[,list(sumA=sum(get(aref))),by=b][,c:=letters[i]]
    dt2
  })
  return(rbindlist(ff))
}
for(i in 1:10) {
  f <- fff("a")
  rm("f")
  gc()
  print(pryr::mem_used())
}

我希望打印的内存使用量保持不变(因为f被删除并且gc()被调用)但它会上升。 请注意,它总是需要一些时间。 添加[,c:=letters[i]]会导致问题更快,但不是必需的(您可能需要将 10 增加到 200 或什么)

示例输出

66.6 MB
66.6 MB
66.6 MB
66.6 MB
87.3 MB
190 MB

# Output of sessionInfo()

R Under development (unstable) (2018-05-10 r74708)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.1252 
[2] LC_CTYPE=English_United States.1252   
[3] LC_MONETARY=English_United States.1252
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] data.table_1.11.3

loaded via a namespace (and not attached):
[1] compiler_3.6.0   pryr_0.1.4       magrittr_1.5     tools_3.6.0     
[5] Rcpp_0.12.16     stringi_1.1.7    codetools_0.2-15 stringr_1.3.0  

在 SO

这是CRAN版本和data.table的Git Hub版本的问题。

最有用的评论

Markus:我不应该写 RAM 使用情况。 我想到的更具体的是缓存,而不是 RAM。 我的笔记本电脑有 32KB 的 L1d、256K 的 L2、6MB L3。 当我们使用多个线程时,所有线程(以及操作系统和其他进程)共享同一个缓存。 因此,如果 data.table 使用 4 个线程,则将这些缓存数字除以 4。 服务器上的缓存通常不会大很多,因此如果服务器上有 32 个线程,则将缓存除以 32。 如果我们创建一个新的 10,000 项跳跃向量,那么 78KB 的权重会通过缓存晃动。 每次使用该向量都需要该元素的缓存行(x86 上为 64 字节)。 但是,考虑一下,在 data.table 的情况下,大多数情况下它不会是一个 _extra_ 跳跃向量,其中基本 SEXP 向量也通过缓存晃动_,而是并行代码将使用跳跃向量_而不是_基本 SEXP 向量。 那应该没问题,我同意即使对于 1e6 列 dt (8MB),工作字节的 ncol*8 也不是问题。 在并行区域内使用原始 C 指针当然更干净,而且可能更快,所以我对此感到非常满意。 是的,非常感谢任何帮助。 我也在找,所以我们应该协调一下。

谢谢卢克 - 理解并再次感谢。 我在 STRSXP 和 VECSXP 上故意违反写屏障的唯一地方是reorder.c 。 这是一个独特的特殊操作:shuffle,例如由setkey() 。 那里绝对保证STRSXP的所有成员之后都还在,他们只是换了位置。 假设有 8 个 STRSXP 列和 8 个线程。 每个线程获得一列,并且这 8 列并行混洗(指针在向量内移动),但不使用 SET_STRING_ELT。 这只是可以,因为它是洗牌。 否则, data.table 会仔细考虑写屏障(至少,它是打算这样做的)。

对于那些想知道“写屏障”是什么意思的阅读者(我也花了一段时间才理解它,在 Luke 过去的帮助下),粗略地说,SET_STRING_ELT 不只是分配指针(到 CHARSXP 中的全局字符缓存)到向量,但它也会增加被分配字符串的引用计数并减少被替换字符串的引用计数。 引用计数在单个全局 CHARSXP 上(即在线程之间共享)。 因此,如果两个线程同时执行此操作,则引用计数可能会混乱(竞争)。 因为增量(例如 C 中的val++ )实际上是机器级别的几个操作:读取、添加、分配。 两个线程可以同时读取,然后将相同的结果写回变量,导致丢失一个增量并最终在其他地方崩溃。 如果val在线程之间共享,则val++是一个写操作,它在 C 中不是线程安全的。 data.table 中的并行 C 代码已经意识到这样的事情并且应该是安全的。 改变的是像 INTEGER() 这样的 R API 函数不再像以前那样是线程安全的(我们依赖于未记录的行为)。 另一件要注意的事情是 data.table 定义了 USE_RINTERNALS,它允许 data.table 使用不属于 R API 的 R 内部; 例如 DATAPTR。 Luke 和 R-core 最终希望 data.table 移除 USE_RINTERNALS 的使用,而只使用 R 的官方 C API。 在 INTEGER() 不再是线程安全的情况下,它不会有帮助,因为 INTEGER 是 R 的 C API 的一部分。 相反,在 INTEGER() 中,我们依赖于未记录的 API 函数线程安全性。 一般来说,由于我们在 C API 后面使用了 USE_RINTERNALS,data.table 确实使 Luke 的工作更难改变 R 本身。 我们的目标也是删除它,并且 CRAN 政策可能会在未来非常合理地进行修订,以不允许 USE_RINTERNALS。 通过删除 data.table.h 中的 #define 并查看中断的内容(目前有很多,例如 DATAPTR 的 73 次使用),可以轻松地对其进行量化。 如果我们现在开始,我们会发现是否真的有什么不能克服的,然后我们可以及时开始与卢克的讨论,如果可以的话,可能会提出我们对官方 C API 添加的请求一个令人信服的案例。 最初,多年前,我有动力使用 USE_RINTERNALS,因为这将 INTEGER 从函数更改为宏并且速度更快。 这不再是真的,所以我们应该重新审视。 另一个原因是 DATAPTR 是为了代码简洁而访问向量内容的一种方便的通用方式。 但是 DATAPTR 现在可以分配,因此它不再只是来自 SEXP 的偏移量。

所有36条评论

我要补充一点,对get的调用似乎是必要的( sum(get(aref)) ),但调用不需要在函数调用中。 所以,这有效

# NOTE: sum(get(aref)) now sum(a)
require(data.table)
n <- 2e6
df <- data.frame(a=rnorm(n),
                 b=factor(rbinom(n,5,prob=0.5),1:5,letters[1:5]),
                 c=factor(rbinom(n,5,prob=0.5),1:5,letters[1:5]))
dt <- setDT(df)
print(pryr::mem_used())
aref <- "a"
for(i in 1:10) {
  ff <- lapply(1:5, function(i) {
    dt2 <- dt[,list(sumA=sum(a)),by=b][,c:=letters[i]]
    dt2
  })
  f <- rbindlist(ff)
  rm("f")
  gc()
  print(pryr::mem_used())
}
# no memory leak
#66.6 MB (repeated)

但这会导致内存泄漏

# NOTE: no fff function
require(data.table)
n <- 2e6
df <- data.frame(a=rnorm(n),
                 b=factor(rbinom(n,5,prob=0.5),1:5,letters[1:5]),
                 c=factor(rbinom(n,5,prob=0.5),1:5,letters[1:5]))
dt <- setDT(df)
print(pryr::mem_used())
aref <- "a"
for(i in 1:10) {
  ff <- lapply(1:5, function(i) {
    dt2 <- dt[,list(sumA=sum(get(aref))),by=b][,c:=letters[i]]
    dt2
  })
  f <- rbindlist(ff)
  rm("f")
  gc()
  print(pryr::mem_used())
}
# 66.6 MB
# 170 MB
# 273 MB
# 375 MB

有趣的是,在第二个代码块之后运行第一个代码块,它也有内存泄漏,它无法启动。

在 R 3.6.0 中使用.SDcols也会导致内存泄漏。

会话信息

require(data.table)
sessionInfo()
# R Under development (unstable) (2018-05-10 r74708)
# Platform: x86_64-w64-mingw32/x64 (64-bit)
# Running under: Windows 7 x64 (build 7601) Service Pack 1
# 
# Matrix products: default
# 
# locale:
#   [1] LC_COLLATE=English_United States.1252  LC_CTYPE=English_United States.1252   
# [3] LC_MONETARY=English_United States.1252 LC_NUMERIC=C                          
# [5] LC_TIME=English_United States.1252    
# 
# attached base packages:
#   [1] stats     graphics  grDevices utils     datasets  methods   base     
# 
# other attached packages:
#   [1] data.table_1.11.2
# 
# loaded via a namespace (and not attached):
#   [1] compiler_3.6.0 tools_3.6.0 

例子:

仅当.SDcols的长度大于 1 时才会发生内存泄漏。

n <- 2e6
df <- data.frame(a=rnorm(n),
                 b=factor(rbinom(n,5,prob=0.5),1:5,letters[1:5]),
                 c=factor(rbinom(n,5,prob=0.5),1:5,letters[1:5]))
setDT(df)

fff2 <- function(colList) {
  ff <- lapply(1:5, function(i) {
    df[,lapply(.SD,sum),by=b,.SDcols = colList]
  })
  return(NULL)
}
gc()
print(pryr::mem_used())
# .SDcols of length 1 does not have memory leak
for(i in 1:10) {
  f <- fff2(c("a"))
  gc()
  print(pryr::mem_used())
}
# 93 MB
# 93.1 MB
# 93.1 MB
# 93.1 MB
# 93.1 MB
# 93.1 MB
# 93.1 MB
# 93.1 MB
# 93.1 MB
# 93.1 MB

# .SDcols of length > 1 has memory leak
for(i in 1:10) {
  f <- fff2(c("a","a"))
  gc()
  print(pryr::mem_used())
}
# 93.1 MB
# 93.1 MB
# 148 MB
# 291 MB
# 433 MB
# 576 MB
# 719 MB
# 862 MB
# 1 GB
# 1.15 GB

我在使用最新 r-devel 的 Linux 上没有看到这些示例的泄漏(嗯,截至 5 月 2 日,在那之前我们已经看到了这个问题)。 看起来可能仅限 Windows,与 #2767 一致。 这将意味着在 Windows 上进行调试,这是我以前从未做过的。 我很惊讶像这样的泄漏仅适用于 Windows,因为这更多地与 R 的内部堆有关,据我所知,它与平台无关。

几天前我们第一次看到这个,如果有帮助的话。

@pdbailey0是的,这有帮助。 你是不是每天都使用R-devel每日快照超过几个星期,然后前几天才看到问题?

@nguyentr17在 3.5.0 预发布版中没有看到这个问题,我在 3.6.0 预发布版中看到了,我们知道他们切换的时候吗? 我突然想到我不知道她多久更新她的 R-devel。

好,谢谢。 我将笔记本电脑上的 R-devel 更新到 2018-05-10 (r74708),但在 Linux 上仍然没有泄漏。 我逐字使用了您的测试,并将循环次数也增加到了 100。 我们只需要硬着头皮使用 Windows 来追踪它......

可以在 Windows R-devel 上重现。

当我尝试提交我的包时,Windows 版本有一个错误,我认为这表明 data.table 现在在 CRAN 的 Windows 安装中不可用。

** byte-compile and prepare package for lazy loading
Warning: S3 methods 'all.equal.data.table', 'groupingsets.data.table', 'cube.data.table', 'rollup.data.table', '[.data.table', '[<-.data.table', '$<-.data.table', 'print.data.table', 'as.data.table.data.table', 'as.data.table.data.frame', 'as.data.table.array', 'as.data.table.matrix', 'as.data.table.list', 'as.data.table.integer', 'as.data.table.numeric', 'as.data.table.character', 'as.data.table.logical', 'as.data.table.factor', 'as.data.table.ordered', 'as.data.table.Date', 'as.data.table.ITime', 'as.data.table.table', 'as.data.table.xts', 'as.data.table.default', 'as.data.frame.data.table', 'as.list.data.table', 'as.matrix.data.table', 'split.data.table', 'dim.data.table', 'dimnames.data.table', 'dimnames<-.data.table', 'names<-.data.table', 'duplicated.data.table', 'unique.data.table', 'merge.data.table', 'subset.data.table', 'transform.data.table', 'within.data.table', 'is.na.data.table', 'format.data.table', 'Ops.data.table', 'anyDuplicated.data.table', 'melt.data.table', 'dcast.data.tabl [... truncated]
Error in library.dynam(lib, package, package.lib) : 
  DLL 'datatable' not found: maybe not installed for this architecture?

我成功地在 2018-05-13 更新了一个包。 我怀疑您遇到了竞争条件:尝试重新提交。

@HughParsonage ,你是对的,我重新提交并且 DLL 存在。 不幸的是,内存泄漏导致 CRAN 运行我们的内存,而我未能通过测试。 我在 R-package-devel 上发布了关于我的包提交的直接问题的帮助。

谢谢@pdbailey0 :你能提供一个你试图提交的包的链接吗? 我在你的 GitHub 个人资料上看不到它。

澄清一下,我认为这不是您的包裹的问题。

提示:使用OMP_NUM_THREADS=1会使内存问题消失。

ALTREP对象调用DATAPTR暂时禁用 GC。 在没有同步的情况下从多个线程调用DATAPTR会创建一个竞争条件,可能会导致 GC 被禁用,因此内存使用量会增加。 这不是泄漏:发出用户中断信号或任何触发跳转到顶层的信号,都会重新启用 GC。

@ltierney为什么跨操作系统不一致?

@jangorecki它是一种竞争条件。 所有平台都存在风险。 当它在平台内因运行而异时,咬的可能性因平台而异,尤其是在线程实现中的性能差异。 在getDTthreads()返回 4 的 Ubuntu 系统上,此变体在大多数运行中都显示了问题:

require(data.table)
n <- 20
df <- data.frame(a=rnorm(n),
                 b=factor(rbinom(n,5,prob=0.5),1:5,letters[1:5]),
                 c=factor(rbinom(n,5,prob=0.5),1:5,letters[1:5]))
dt <- setDT(df)
v <- gc()
print(sum(v[, 2]))
fff <- function(aref) {
  ff <- lapply(1:5, function(i) {
    dt2 <- dt[,list(sumA=sum(get(aref))),by=b][,c:=letters[i]]
    dt2
  })
  return(rbindlist(ff))
}
for(i in 1:100) {
  f <- fff("a")
  rm("f")
  v <- gc()
  print(sum(v[, 2]))
}

非常感谢@ltierney ! 我在reorder.c看到并行区域内的 DATAPTR 使用情况。 我将从那里开始...

我可以确认使用setDTthreads(1)为我修复了这个并将其设置回 2+ 使代码显示内存膨胀。 我无法让问题出现在 3.5.0 中。

INTEGER、REAL 等调用 DATAPTR 因此也不安全。 我相信是
参与具体报告的INTEGER。 会最安全
如果可以的话,从并行区域中获取对 R 的任何调用。

2018 年 5 月 15 日,星期二,马特·道尔写道:

非常感谢@ltierney ! 我看到并行区域内的 DATAPTR 使用
重新排序.c. 我将从那里开始...


你收到这个是因为你被提到了。
直接回复这封邮件,在 GitHub 上查看,或者静音
线程。[ADvaVBgXTt6rdXNrdJXZoM1cY3xhgidNks5tyz6lgaJpZM4T78v2.gif]

——
卢克·蒂尔尼
Ralph E. Wareham 数学科学教授
爱荷华大学电话:319-335-3386
统计与传真部:319-335-3017
精算学
241 Schaeffer Hall 电子邮件: [email protected]
爱荷华市,IA 52242 WWW: http :

刚才的那个提交在本地为我修复了 reprex。 这确实是 ALTREP 上并行区域内的 INTEGER。 事实证明,在subset.c不是reorder.c 。 现在,它只是在它传递的任何 ALTREP 上调用duplicate() 。 我看到duplicate(x)调用duplicate1(x, deep=TRUE)所以我猜我可以依靠它来扩展 ALTREP (因为重复 1 隐藏在包使用中)。

如果这有效,我将类似地扫过所有平行区域。

我看到您将n到 20 以在 Ubuntu 上重现此内容。 n2e6在最初报告的 Windows reprex 中。 您是否将n变小了,以便向量位于 R 堆上? 我想我记得你之前告诉我 Linux R 现在在操作系统堆上分配大向量,但 Windows R 仍然将它们放在 R 堆上。 如果是这样,那将是 2e6 大小的这个特定 reprex 的明显 Windows 性质的一个促成因素?

2018 年 5 月 15 日,星期二,马特·道尔写道:

刚才的那个提交在本地为我修复了 reprex。 这个确实是
ALTREP 上并行区域内的整数。 在subset.c 中不要像它那样重新排序.c
结果是。 现在,它只是在它传递的任何 ALTREP 上调用重复()。 一世
看到duplicate(x) 调用duplicate1(x, deep=TRUE) 所以我猜我可以
依靠它来扩展 ALTREP(因为重复 1 从包中隐藏
用)。

如果这有效,我将类似地扫过所有平行区域。

我看到您将 n 减少到 20 以在 Ubuntu 上重现此内容。 n 是 2e6
Windows reprex 最初报道。 你有没有把 n 变小,以便
向量会在 R 堆上吗? 我想我记得你在那之前告诉过我
Linux R 现在在操作系统堆上分配大向量,但 Windows R 仍然放置
它们在 R 堆上。 如果是这样,那将是导致
2e6 大小的这个特定 reprex 的明显 Windows 性质?

没有那么复杂——我只是想看看我是否可以
改变工作负载时间,使全局更新发生在
错误的顺序。 我在 github 上发布的版本现在不再失败
我在 Ubuntu 上——这是竞争条件的诅咒。 但是这个
在带有两个线程的 Ubuntu 上崩溃和烧毁非常严重且可靠
(至少目前); 它是好的单线程。 这也失败
对我来说,在 R-patched 中,也在 Ubuntu 上。

require(data.table)
n <- 20

mkDT <- function() {
     df <- data.frame(a = 1:n,
                      b = 1:n,
                      c = 1:n,
                      d = 1:n,
                      e = 1:n,
                      f = 1:n,
                      h = 1:n,
                      i = 1:n)
     setDT(df)
}

print(sum(gc()[, 2]))
fff <- function(aref) {
     dt <- mkDT()
     lapply(1:5, function(i) {
         dt2 <- dt[,list(sumA=sum(get(aref))),by=b][,c:=letters[i]]
         dt2
     })
}

for(i in 1:100) {
   fff("a")
   print(sum(gc()[, 2]))
}

最好的事物,

卢克


你收到这个是因为你被提到了。
直接回复这封邮件,在 GitHub 上查看,或者静音
线程。[ADvaVJwGR4gm37L0TqXo64tT8jj9KL-Kks5ty17dgaJpZM4T78v2.gif]

——
卢克·蒂尔尼
Ralph E. Wareham 数学科学教授
爱荷华大学电话:319-335-3386
统计与传真部:319-335-3017
精算学
241 Schaeffer Hall 电子邮件: [email protected]
爱荷华市,IA 52242 WWW: http :

非常感谢卢克! 如果不是你,我们会被困数周(也许更长)。

这个对我来说也失败了,但优雅地使用DT contains an altrep 。 我在上次提交中添加了该错误。 现将地址。 [我之前写过 error() 调用在一个并行区域内,我知道它是不安全的,但实际上它不是,所以应该是可靠的优雅错误,至少在目前的这个分支中是这样。 ]

测试套件现在似乎正在通过 AppVeyor R-devel,这是一个月左右的第一次!!! 时间会告诉我们的。 我会整理subset.c 并合并它来修复这个特定的reprex,这样人们就可以开始测试1.11.3。 我会继续扫荡其他平行区域。

2018 年 5 月 15 日,星期二,马特·道尔写道:

非常感谢卢克! 如果它,我们将被困数周(也许更长)
不是给你的。

这个对我来说也失败了,但优雅地使用 DT 包含一个 altrep。 我只是
在上次提交中添加了该错误。 但是那个 error() 调用是在一个
并行区域,因此也很可能崩溃和烧毁。 这只是一个
临时行(我知道错误不是线程安全的),我现在将解决这个问题。

似乎测试套件现在正在通过 AppVeyor R-devel,这是第一次
一个月左右的时间!!! 时间会告诉我们的。 我会整理subset.c并合并它
修复这个特定的 reprex,以便用户可以开始测试它在
1.11.3. 我会继续扫荡其他平行区域。

另一个快速说明:您提到对 ALTREP 使用重复项。
不保证重复项会返回标准向量——ALTREP
类可以定义自己的方法来做其他事情。 相当
比测试 ALTREP 并复制更好的方法是移动
调用 REAL 和任何其他可能不是线程安全的东西
或将它们放在临界区。

最好的事物,

卢克


你收到这个是因为你被提到了。
直接回复这封邮件,在 GitHub 上查看,或者静音
线程。[ADvaVFmo-9G5aIk9G4nQMuiKVeZqdyagks5ty2mmgaJpZM4T78v2.gif]

——
卢克·蒂尔尼
Ralph E. Wareham 数学科学教授
爱荷华大学电话:319-335-3386
统计与传真部:319-335-3017
精算学
241 Schaeffer Hall 电子邮件: [email protected]
爱荷华市,IA 52242 WWW: http :

将它们移出的麻烦在于,我通常必须分配一个新向量来保存安全指针的向量(INTEGER 和 REAL 返回的内容)。 对于 10,000 列或更多列的 data.tables,存在 RAM 使用问题,但这也与所需的额外代码有关。 虽然我想这样做可能会更快,因为现在 iiuc、INTEGER 和 REAL 稍微重一些。

作为包使用的选项,是否有可靠的方法来扩展 ALTREP 向量?

我不认为指针向量的内存分配是一个问题。 指针最大为 64 位。 所以,如果我没记错的话, 1e6列需要 ~ 8MB 。 如果 data.table 包含所有整数列,这将对应于另外两行所需的内存(假设为 32 位整数)。 我想,这绝对是可以接受的。

我更关心的是CHARSXP s 和VECSXP s。 甚至有可能摆脱SET_STRING_ELTSET_VECTOR_ELT并用本机 C 代码替换它们吗? 但后来我读到ELT方法确实如此

Set_elt - 不使用 dataptr 设置元素 i 的值

(https://gmbecker.github.io/jsm2017/jsm2017.html)。

所以这可能不是问题,因为他们明确不需要DATAPTR
我建议从长远来看审查 C 代码,以尽可能将 R 的内容移出并行代码。 我很乐意为此提供帮助。 在短时间内,如果可能的话,扩展 ALTREP 似乎是一个快速而简单的解决方案。

2018 年 5 月 16 日星期三,MarkusBonsch 写道:

我不认为指针向量的内存分配是一个
问题。 指针最大为 64 位。 因此,1e6 列将
如果我没记错的话,需要~8MB。 如果 data.table 包含所有
整数列,这将对应于两个所需的内存
附加行(假设为 32 位整数)。 我想,这绝对是
可以接受。

我更关心的是 CHARSXP 和 VECSXP。 甚至有可能得到
摆脱 SET_STRING_ELT 和 SET_VECTOR_ELT 并用本机 C 代码替换它们?
但后来我读到 ELT 方法确实如此

  Set_elt - Set the value of element i without using dataptr

您只能通过 ELT 函数修改 STRSXP 和 VECSXP,因为
写屏障需要看到修改。

(https://gmbecker.github.io/jsm2017/jsm2017.html)。

更多信息在这里:

https://svn.r-project.org/R/branches/ALTREP/ALTREP.html

所以这可能不是问题,因为他们明确不需要
数据表?

他们可能仍然分配。 此时,DATAPTR、STRING_ELT 和
SET_STRING_ELT 是现在可以分配的函数。 可能还有更多
随着我们前进。

任何可能分配的东西都可能发出错误信号,因为分配
失败的。 DATAPTR 也可以发出错误信号,因为特定的 ALTREP
类不能提供指向连续地址范围的指针。 也许有
是一些其他功能,现在可能会发出错误信号,但可能不会
以前有。

任何可能修改内部 R 数据结构的东西都必须是
一次只能被一个线程访问。 任何可能预示着
必须从主线程调用错误,否则跳转将出现段错误。
即使从主线程跳转也可能搞乱 OMP 线程管理
代码,如果它发生在并行区域内。 所以基本上只有
安全的选择是将调用 R 的所有内容移出
平行区域。

我建议从长远来看审查 C 代码以尽可能地移动 R 的东西
尽可能出并行代码。 我很乐意为此提供帮助。 简而言之
请注意,如果可靠,扩展 ALTREP 似乎是一个快速简便的解决方案
可能的。

扩展的概念不够普遍。 ALTREP 旨在允许
无法获得指向连续地址空间的指针的对象
可行(例如大型分布式阵列)。 需要一个代码
DATAPTR 将在这些事情上失败,但已写入的代码
在 DATAPTR 不可用时处理一个块将起作用。

最好的事物,

卢克


你收到这个是因为你被提到了。
直接回复这封邮件,在 GitHub 上查看,或者静音
线程。[ADvaVHF7pM3hAcQoCxXuIFzb3lxVYpJiks5ty9sJgaJpZM4T78v2.gif]

——
卢克·蒂尔尼
Ralph E. Wareham 数学科学教授
爱荷华大学电话:319-335-3386
统计与传真部:319-335-3017
精算学
241 Schaeffer Hall 电子邮件: [email protected]
爱荷华市,IA 52242 WWW: http :

对于任何寻找解决方法的人,我将其包含在.onAttach

using_r_devel <- grepl(pattern = "devel", x = R.version$status)
if (using_r_devel) {
    requireNamespace("data.table")
    data.table::setDTthreads(threads = 1L)
}

它通过了 Winbuilder。

Markus:我不应该写 RAM 使用情况。 我想到的更具体的是缓存,而不是 RAM。 我的笔记本电脑有 32KB 的 L1d、256K 的 L2、6MB L3。 当我们使用多个线程时,所有线程(以及操作系统和其他进程)共享同一个缓存。 因此,如果 data.table 使用 4 个线程,则将这些缓存数字除以 4。 服务器上的缓存通常不会大很多,因此如果服务器上有 32 个线程,则将缓存除以 32。 如果我们创建一个新的 10,000 项跳跃向量,那么 78KB 的权重会通过缓存晃动。 每次使用该向量都需要该元素的缓存行(x86 上为 64 字节)。 但是,考虑一下,在 data.table 的情况下,大多数情况下它不会是一个 _extra_ 跳跃向量,其中基本 SEXP 向量也通过缓存晃动_,而是并行代码将使用跳跃向量_而不是_基本 SEXP 向量。 那应该没问题,我同意即使对于 1e6 列 dt (8MB),工作字节的 ncol*8 也不是问题。 在并行区域内使用原始 C 指针当然更干净,而且可能更快,所以我对此感到非常满意。 是的,非常感谢任何帮助。 我也在找,所以我们应该协调一下。

谢谢卢克 - 理解并再次感谢。 我在 STRSXP 和 VECSXP 上故意违反写屏障的唯一地方是reorder.c 。 这是一个独特的特殊操作:shuffle,例如由setkey() 。 那里绝对保证STRSXP的所有成员之后都还在,他们只是换了位置。 假设有 8 个 STRSXP 列和 8 个线程。 每个线程获得一列,并且这 8 列并行混洗(指针在向量内移动),但不使用 SET_STRING_ELT。 这只是可以,因为它是洗牌。 否则, data.table 会仔细考虑写屏障(至少,它是打算这样做的)。

对于那些想知道“写屏障”是什么意思的阅读者(我也花了一段时间才理解它,在 Luke 过去的帮助下),粗略地说,SET_STRING_ELT 不只是分配指针(到 CHARSXP 中的全局字符缓存)到向量,但它也会增加被分配字符串的引用计数并减少被替换字符串的引用计数。 引用计数在单个全局 CHARSXP 上(即在线程之间共享)。 因此,如果两个线程同时执行此操作,则引用计数可能会混乱(竞争)。 因为增量(例如 C 中的val++ )实际上是机器级别的几个操作:读取、添加、分配。 两个线程可以同时读取,然后将相同的结果写回变量,导致丢失一个增量并最终在其他地方崩溃。 如果val在线程之间共享,则val++是一个写操作,它在 C 中不是线程安全的。 data.table 中的并行 C 代码已经意识到这样的事情并且应该是安全的。 改变的是像 INTEGER() 这样的 R API 函数不再像以前那样是线程安全的(我们依赖于未记录的行为)。 另一件要注意的事情是 data.table 定义了 USE_RINTERNALS,它允许 data.table 使用不属于 R API 的 R 内部; 例如 DATAPTR。 Luke 和 R-core 最终希望 data.table 移除 USE_RINTERNALS 的使用,而只使用 R 的官方 C API。 在 INTEGER() 不再是线程安全的情况下,它不会有帮助,因为 INTEGER 是 R 的 C API 的一部分。 相反,在 INTEGER() 中,我们依赖于未记录的 API 函数线程安全性。 一般来说,由于我们在 C API 后面使用了 USE_RINTERNALS,data.table 确实使 Luke 的工作更难改变 R 本身。 我们的目标也是删除它,并且 CRAN 政策可能会在未来非常合理地进行修订,以不允许 USE_RINTERNALS。 通过删除 data.table.h 中的 #define 并查看中断的内容(目前有很多,例如 DATAPTR 的 73 次使用),可以轻松地对其进行量化。 如果我们现在开始,我们会发现是否真的有什么不能克服的,然后我们可以及时开始与卢克的讨论,如果可以的话,可能会提出我们对官方 C API 添加的请求一个令人信服的案例。 最初,多年前,我有动力使用 USE_RINTERNALS,因为这将 INTEGER 从函数更改为宏并且速度更快。 这不再是真的,所以我们应该重新审视。 另一个原因是 DATAPTR 是为了代码简洁而访问向量内容的一种方便的通用方式。 但是 DATAPTR 现在可以分配,因此它不再只是来自 SEXP 的偏移量。

@mattdowle我以为只有freadfwriteforder是多线程的(而且最近才变得如此)。 不是这样吗? 换句话说,data.table 在多大程度上依赖于 INTEGER 的线程安全?

@ltierney@mattdowle 非常感谢您的解释和材料,我学到了很多。
@mattdowle我明白了,缓存是不同的,但我会像你一样看待它:我们只是用缓存中的 C 指针向量替换 SEXP 向量。 在我开始之前,我还有一些其他问题需要澄清,我之前会与您联系,所以我们不会发生冲突。

Hugh:有点多: setkey()并行重新排序列,C 级别的subsetDT是 C 中[.data.table替换的开始,并且在某些情况下是并行的,并且fsort是并行的,但由于被标记为实验性的,所以使用不多; Markus 是第一个在 data.table 内部使用它的人,因为他发现将整数强制加倍然后使用 fsort(目前仅对 double 实现)更快并且值得强制成本。 他可能已经把 fsort 和 fsort 联系起来了,我不太记得了。 grep用于查找所有并行区域的“pragma”的来源。

对不起,如果你预料到这一点,但我正要测试我的包与这个新版本。 首先,我抓住了 master,构建了它(在 3.5.0 中)并在 3.6.0 中安装了它,并且仍然从我的测试用例(在 OP 中)中获得了显着增加的内存大小。

74.9 MB
116 MB
219 MB
322 MB
425 MB
528 MB
631 MB
733 MB
836 MB
939 MB

来自 Luke 的示例(此处未显示更长的输出)。

@pdbailey0感谢您的检查。 您能否使用 AppVeyor 构建的二进制 .zip 并按照此处的段落重新启动您的 Windows 机器。 怀疑是升级/dll 问题。 另外,请问 test.data.table() 返回什么?

好的,太好了,现在可以使用了,谢谢! 它在我的包裹中也很好用。

嗨,抱歉,我刚刚阅读了您的 1.14.4 新闻,并想在信用到期时给予信用。 @nguyentr17 (Trang Nguyen) 提出了最小的工作示例。 抱歉,我上面没有更清楚。 你能把这归因于她而不是我吗?

当然。 希望刚才编辑cab7c11没问题。 非常感谢@nguyentr17

感谢您修复它!

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

rafapereirabr picture rafapereirabr  ·  3评论

jangorecki picture jangorecki  ·  3评论

tcederquist picture tcederquist  ·  3评论

nachti picture nachti  ·  3评论

mattdowle picture mattdowle  ·  3评论