我在组中使用按组引用分配时看到奇数内存使用情况data.table
.这是一个简单的示例(请原谅示例的无关紧要):
N <- 1e6 dt <- data.table(id=round(rnorm(N)), value=rnorm(N)) gc() for (i in seq(100)) { dt[, value := value+1, by="id"] } gc() tables()
产生以下输出:
> gc() used (Mb) gc trigger (Mb) max used (Mb) Ncells 303909 16.3 597831 32.0 407500 21.8 Vcells 2442853 18.7 3260814 24.9 2689450 20.6 > for (i in seq(100)) { + dt[, value := value+1, by="id"] + } > gc() used (Mb) gc trigger (Mb) max used (Mb) Ncells 315907 16.9 597831 32.0 407500 21.8 Vcells 59966825 457.6 73320781 559.4 69633650 531.3 > tables() NAME NROW MB COLS KEY [1,] dt 1,000,000 16 id,value Total: 16MB
因此在循环之后添加了大约440MB的使用过的Vcells内存.从内存中删除data.table后,不考虑此内存:
> rm(dt) > gc() used (Mb) gc trigger (Mb) max used (Mb) Ncells 320888 17.2 597831 32 407500 21.8 Vcells 57977069 442.4 77066820 588 69633650 531.3 > tables() No objects of class data.table exist in .GlobalEnv
从赋值中删除by = ...时,内存泄漏似乎消失了:
> gc() used (Mb) gc trigger (Mb) max used (Mb) Ncells 312955 16.8 597831 32.0 467875 25.0 Vcells 2458890 18.8 3279586 25.1 2704448 20.7 > for (i in seq(100)) { + dt[, value := value+1] + } > gc() used (Mb) gc trigger (Mb) max used (Mb) Ncells 322698 17.3 597831 32.0 467875 25.0 Vcells 2478772 19.0 5826337 44.5 5139567 39.3 > tables() NAME NROW MB COLS KEY [1,] dt 1,000,000 16 id,value Total: 16MB
总结一下,有两个问题:
我错过了什么或是否有内存泄漏?
如果确实存在内存泄漏,是否有人可以建议一种解决方法,让我按组引用使用赋值而不会出现内存泄漏?
供参考,这是输出sessionInfo()
:
R version 3.0.2 (2013-09-25) Platform: x86_64-pc-linux-gnu (64-bit) locale: [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8 LC_MONETARY=en_US.UTF-8 [6] LC_MESSAGES=en_US.UTF-8 LC_PAPER=en_US.UTF-8 LC_NAME=C LC_ADDRESS=C LC_TELEPHONE=C [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C attached base packages: [1] stats graphics grDevices utils datasets methods base other attached packages: [1] data.table_1.8.10 loaded via a namespace (and not attached): [1] tools_3.0.2
Arun.. 6
来自马特的更新 - 现在修复于v1.8.11.来自新闻:
分组固定时长时间(通常很小)内存泄漏.当最后一组小于最大组时,这些大小的差异未被释放.大多数用户运行一次分组查询并且永远不会注意到,但是任何循环调用分组的人(例如并行运行或基准测试时)都可能遭受损失,#2648.测试补充.
非常感谢vc273,YT和其他人.
从阿伦...
为什么会这样?
我希望在谈到这个问题之前我曾经遇到过这个帖子.然而,一个很好的学习经历.Simon Urbanek非常简洁地总结了这个问题,它不是内存泄漏,而是使用/释放内存的错误报告.我感觉这就是发生的事情.
发生这种情况的原因是什么data.table
?这部分是dogroups.c
为了确定代码的一部分,负责显着的内存增加.
好吧,经过一些繁琐的测试后,我想我至少找到了导致这种情况发生的原因.希望有人可以帮助我从这篇文章到达那里.我的结论是,这是不是一个内存泄漏.
简短的解释是,这似乎是SETLENGTH
data.table 中函数(来自R的C接口)的使用效果dogroups.c
.
在data.table
,当您使用时by=...
,例如,
set.seed(45) DT <- data.table(x=sample(3, 12, TRUE), id=rep(3:1, c(2,4,6))) DT[, list(y=mean(x)), by=id]
对应于id=1
,=c(1,2,1,1,2,3)
必须选择"x"()的值.这意味着,必须为每个值分配.SD
(所有列不在by
)的内存by
.
在克服这种分配对各组by
,data.table
由第一分配巧妙地实现此目的.SD
具有最大组中的长度by
(其在这里被对应于id=1
,长度6).然后,我们可以是,的每个值id
,再利用的(过度)分配data.table并通过使用函数SETLENGTH
,我们可以只调整长度以当前组的长度.请注意,通过执行此操作,此处不会实际分配任何内存,除了为最大组分配的内存.
但看起来很奇怪的是,当每个组中的元素数量by
都具有相同数量的项目时,在gc()
输出方面似乎没有什么特别的事情发生.但是,当它们不相同时,gc()
似乎报告了Vcells的使用量增加.尽管在这两种情况下都没有分配额外的内存.
为了说明这一点,我写了一个C-代码,模仿的SETLENGTH
功能使用中dogroups.c
的`data.table.
// test.c #include#define USE_RINTERNALS #include #include int sizes[100]; #define SIZEOF(x) sizes[TYPEOF(x)] // test function - no checks! SEXP test(SEXP vec, SEXP SD, SEXP lengths) { R_len_t i, j; char before_address[32], after_address[32]; SEXP tmp, ans; PROTECT(tmp = allocVector(INTSXP, 1)); PROTECT(ans = allocVector(STRSXP, 2)); snprintf(before_address, 32, "%p", (void *)SD); for (i=0; i 这里
vec
是等同于任何data.tabledt
和SD
等效于.SD
和lengths
位于每个组的长度.这只是一个虚拟程序.基本上对于每个值lengths
,比如说n
,第一个n
元素都是从vec
上复制到的SD
.然后可以计算出这个SD上想要的任何东西(这里没有做到).出于我们的目的,返回使用SETLENGTH操作之前和之后的SD地址,以说明SETLENGTH没有复制.将此文件另存为
test.c
,然后从终端编译如下:R CMD SHLIB -o test.so test.c现在,打开一个新的R-session,转到
test.so
存在的路径,然后键入:dyn.load("test.so") require(data.table) set.seed(45) max_len <- as.integer(1e6) lengths <- as.integer(sample(4:(max_len)/10, max_len/10)) gc() vec <- 1:max_len for (i in 1:100) { SD <- vec[1:max(lengths)] bla <- .Call("test", vec, SD, lengths) print(gc()) }请注意,对于每个
i
这里,.SD
将分配一个不同的内存位置,并通过SD
为每个位置分配来复制i
.通过运行此代码,您将发现1)返回的两个值对于每个值都是相同的
i
,address(SD)
并且2)Vcells used Mb
保持增加.现在,从工作区中删除所有变量rm(list=ls())
,然后执行gc()
,您会发现并非所有内存都被恢复/释放.初始:
used (Mb) gc trigger (Mb) max used (Mb) Ncells 332708 17.8 597831 32.0 467875 25.0 Vcells 1033531 7.9 2327578 17.8 2313676 17.7100次运行后:
used (Mb) gc trigger (Mb) max used (Mb) Ncells 332912 17.8 597831 32.0 467875 25.0 Vcells 2631370 20.1 4202816 32.1 2765872 21.2之后
rm(list=ls())
和gc()
:used (Mb) gc trigger (Mb) max used (Mb) Ncells 341275 18.3 597831 32.0 467875 25.0 Vcells 2061531 15.8 4202816 32.1 3121469 23.9如果
SETLENGTH(SD, ...)
从C代码中删除该行并再次运行,您会发现Vcells没有变化.现在,为什么 SETLENGTH对具有不同组长度的分组具有这种效果,
我仍然试图理解- 检查上面编辑中的链接.
来自马特的更新 - 现在修复于v1.8.11.来自新闻:
分组固定时长时间(通常很小)内存泄漏.当最后一组小于最大组时,这些大小的差异未被释放.大多数用户运行一次分组查询并且永远不会注意到,但是任何循环调用分组的人(例如并行运行或基准测试时)都可能遭受损失,#2648.测试补充.
非常感谢vc273,YT和其他人.
从阿伦...
为什么会这样?
我希望在谈到这个问题之前我曾经遇到过这个帖子.然而,一个很好的学习经历.Simon Urbanek非常简洁地总结了这个问题,它不是内存泄漏,而是使用/释放内存的错误报告.我感觉这就是发生的事情.
发生这种情况的原因是什么data.table
?这部分是dogroups.c
为了确定代码的一部分,负责显着的内存增加.
好吧,经过一些繁琐的测试后,我想我至少找到了导致这种情况发生的原因.希望有人可以帮助我从这篇文章到达那里.我的结论是,这是不是一个内存泄漏.
简短的解释是,这似乎是SETLENGTH
data.table 中函数(来自R的C接口)的使用效果dogroups.c
.
在data.table
,当您使用时by=...
,例如,
set.seed(45) DT <- data.table(x=sample(3, 12, TRUE), id=rep(3:1, c(2,4,6))) DT[, list(y=mean(x)), by=id]
对应于id=1
,=c(1,2,1,1,2,3)
必须选择"x"()的值.这意味着,必须为每个值分配.SD
(所有列不在by
)的内存by
.
在克服这种分配对各组by
,data.table
由第一分配巧妙地实现此目的.SD
具有最大组中的长度by
(其在这里被对应于id=1
,长度6).然后,我们可以是,的每个值id
,再利用的(过度)分配data.table并通过使用函数SETLENGTH
,我们可以只调整长度以当前组的长度.请注意,通过执行此操作,此处不会实际分配任何内存,除了为最大组分配的内存.
但看起来很奇怪的是,当每个组中的元素数量by
都具有相同数量的项目时,在gc()
输出方面似乎没有什么特别的事情发生.但是,当它们不相同时,gc()
似乎报告了Vcells的使用量增加.尽管在这两种情况下都没有分配额外的内存.
为了说明这一点,我写了一个C-代码,模仿的SETLENGTH
功能使用中dogroups.c
的`data.table.
// test.c #include <R.h> #define USE_RINTERNALS #include <Rinternals.h> #include <Rdefines.h> int sizes[100]; #define SIZEOF(x) sizes[TYPEOF(x)] // test function - no checks! SEXP test(SEXP vec, SEXP SD, SEXP lengths) { R_len_t i, j; char before_address[32], after_address[32]; SEXP tmp, ans; PROTECT(tmp = allocVector(INTSXP, 1)); PROTECT(ans = allocVector(STRSXP, 2)); snprintf(before_address, 32, "%p", (void *)SD); for (i=0; i<LENGTH(lengths); i++) { memcpy((char *)DATAPTR(SD), (char *)DATAPTR(vec), INTEGER(lengths)[i] * SIZEOF(tmp)); SETLENGTH(SD, INTEGER(lengths)[i]); // do some computation here.. ex: mean(SD) } snprintf(after_address, 32, "%p", (void *)SD); SET_STRING_ELT(ans, 0, mkChar(before_address)); SET_STRING_ELT(ans, 1, mkChar(after_address)); UNPROTECT(2); return(ans); }
这里vec
是等同于任何data.table dt
和SD
等效于.SD
和lengths
位于每个组的长度.这只是一个虚拟程序.基本上对于每个值lengths
,比如说n
,第一个n
元素都是从vec
上复制到的SD
.然后可以计算出这个SD上想要的任何东西(这里没有做到).出于我们的目的,返回使用SETLENGTH操作之前和之后的SD地址,以说明SETLENGTH没有复制.
将此文件另存为test.c
,然后从终端编译如下:
R CMD SHLIB -o test.so test.c
现在,打开一个新的R-session,转到test.so
存在的路径,然后键入:
dyn.load("test.so") require(data.table) set.seed(45) max_len <- as.integer(1e6) lengths <- as.integer(sample(4:(max_len)/10, max_len/10)) gc() vec <- 1:max_len for (i in 1:100) { SD <- vec[1:max(lengths)] bla <- .Call("test", vec, SD, lengths) print(gc()) }
请注意,对于每个i
这里,.SD
将分配一个不同的内存位置,并通过SD
为每个位置分配来复制i
.
通过运行此代码,您将发现1)返回的两个值对于每个值都是相同的i
,address(SD)
并且2)Vcells used Mb
保持增加.现在,从工作区中删除所有变量rm(list=ls())
,然后执行gc()
,您会发现并非所有内存都被恢复/释放.
初始:
used (Mb) gc trigger (Mb) max used (Mb) Ncells 332708 17.8 597831 32.0 467875 25.0 Vcells 1033531 7.9 2327578 17.8 2313676 17.7
100次运行后:
used (Mb) gc trigger (Mb) max used (Mb) Ncells 332912 17.8 597831 32.0 467875 25.0 Vcells 2631370 20.1 4202816 32.1 2765872 21.2
之后rm(list=ls())
和gc()
:
used (Mb) gc trigger (Mb) max used (Mb) Ncells 341275 18.3 597831 32.0 467875 25.0 Vcells 2061531 15.8 4202816 32.1 3121469 23.9
如果SETLENGTH(SD, ...)
从C代码中删除该行并再次运行,您会发现Vcells没有变化.
现在,为什么 SETLENGTH对具有不同组长度的分组具有这种效果,我仍然试图理解 - 检查上面编辑中的链接.