data.table中的内存泄漏按引用分组分配

 爵士独舞 发布于 2023-02-13 11:32

我在组中使用按组引用分配时看到奇数内存使用情况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为了确定代码的一部分,负责显着的内存增加.

好吧,经过一些繁琐的测试后,我想我至少找到了导致这种情况发生的原因.希望有人可以帮助我从这篇文章到达那里.我的结论是,这是不是一个内存泄漏.

简短的解释是,这似乎是SETLENGTHdata.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.table dtSD等效于.SDlengths位于每个组的长度.这只是一个虚拟程序.基本上对于每个值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对具有不同组长度的分组具有这种效果,我仍然试图理解 - 检查上面编辑中的链接.

1 个回答
  • 来自马特的更新 - 现在修复于v1.8.11.来自新闻:

    分组固定时长时间(通常很小)内存泄漏.当最后一组小于最大组时,这些大小的差异未被释放.大多数用户运行一次分组查询并且永远不会注意到,但是任何循环调用分组的人(例如并行运行或基准测试时)都可能遭受损失,#2648.测试补充.

    非常感谢vc273,YT和其他人.



    从阿伦...

    为什么会这样?

    我希望在谈到这个问题之前我曾经遇到过这个帖子.然而,一个很好的学习经历.Simon Urbanek非常简洁地总结了这个问题,它不是内存泄漏,而是使用/释放内存的错误报告.我感觉这就是发生的事情.


    发生这种情况的原因是什么data.table?这部分是dogroups.c为了确定代码的一部分,负责显着的内存增加.

    好吧,经过一些繁琐的测试后,我想我至少找到了导致这种情况发生的原因.希望有人可以帮助我从这篇文章到达那里.我的结论是,这是不是一个内存泄漏.

    简短的解释是,这似乎是SETLENGTHdata.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 dtSD等效于.SDlengths位于每个组的长度.这只是一个虚拟程序.基本上对于每个值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对具有不同组长度的分组具有这种效果,我仍然试图理解 - 检查上面编辑中的链接.

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