我在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
答案 0 :(得分:6)
来自Matt的更新 - 现已修复于v1.8.11。来自NEWS:
分组固定时长时间(通常很小)内存泄漏。什么时候 最后一组小于最大组,差异在于 那些尺寸没有被释放。大多数用户运行分组查询 曾经且永远不会注意到,但是有人将呼叫循环到分组 (例如当并行运行或基准测试时)可能遭受了损失, #2648。测试补充。
非常感谢vc273,Y T和其他人。
来自阿伦...
为什么会这样?
我希望在遇到这个问题之前我遇到this post。然而,一个很好的学习经历。 Simon Urbanek非常简洁地总结了这个问题,它不是内存泄漏,而是使用/释放内存的错误报告。我感觉这就是发生的事情。
data.table
发生这种情况的原因是什么?这部分是为了确定dogroups.c
中代码的一部分,负责显着的内存增加。
好的,经过一些繁琐的测试后,我想我至少找到了导致这种情况发生的原因。希望有人可以帮助我从这篇文章到达那里。我的结论是 内存泄漏。
简短的解释是,这似乎是在data.table的SETLENGTH
中使用dogroups.c
函数(来自R的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
,必须选择“x”(=c(1,2,1,1,2,3)
)的值。这意味着,必须为每个.SD
值的by
(by
}以外的所有列)分配内存。
为了克服by
中每个群组的这种分配,data.table
通过首先在.SD
中分配by
最大群组的长度来巧妙地实现这一点(这里是对应id=1
,长度6)。然后,我们可以为id
的每个值,重新使用(过度)分配的data.table并使用函数SETLENGTH
我们可以将长度调整为当前组的长度。请注意,通过这样做,这里没有实际分配内存,除了为最大组分配的内存。
但似乎奇怪的是,当by
中每个组的元素数量都具有相同数量的项目时,就gc()
输出而言似乎没有什么特别的事情发生。但是,当它们不相同时,gc()
似乎报告Vcells的使用量增加。尽管在这两种情况下都没有分配额外的内存。
为了说明这一点,我在`data.table中编写了一个模仿<{em} SETLENGTH
dogroups.c
函数用法的C代码。
// 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)
的每个Vcells used Mb
都是相同的,并且{2} 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
,您将发现并非所有内存都被还原/释放。
初始:
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
100次运行后:
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代码中删除行{{1}}并再次运行,您会发现Vcells没有变化。
现在为什么 SETLENGTH对具有不同组长度的分组有这种影响,我仍然试图理解 - 检查编辑以上。