重新审视 data.table 与 dplyr 的内存使用

Posted

技术标签:

【中文标题】重新审视 data.table 与 dplyr 的内存使用【英文标题】:data.table vs dplyr memory use revisited 【发布时间】:2020-08-06 04:09:15 【问题描述】:

我知道 data.tabledplyr 比较是 SO 上的常年最爱。 (完全披露:我喜欢并使用这两个包。)

但是,在尝试对我正在教授的课程进行一些比较时,我遇到了一些令人惊讶的事情。内存使用情况。我的期望是 dplyr 在需要(隐式)过滤或数据切片的操作中表现特别差。但这不是我发现的。比较:

第一个 dplyr

library(bench)
library(dplyr, warn.conflicts = FALSE)
library(data.table, warn.conflicts = FALSE)
set.seed(123)

DF = tibble(x = rep(1:10, times = 1e5),
                y = sample(LETTERS[1:10], 10e5, replace = TRUE),
                z = rnorm(1e6))

DF %>% filter(x > 7) %>% group_by(y) %>% summarise(mean(z))
#> # A tibble: 10 x 2
#>    y     `mean(z)`
#>  * <chr>     <dbl>
#>  1 A     -0.00336 
#>  2 B     -0.00702 
#>  3 C      0.00291 
#>  4 D     -0.00430 
#>  5 E     -0.00705 
#>  6 F     -0.00568 
#>  7 G     -0.00344 
#>  8 H      0.000553
#>  9 I     -0.00168 
#> 10 J      0.00661

bench::bench_process_memory()
#> current     max 
#>   585MB   611MB

由reprex package (v0.3.0) 于 2020 年 4 月 22 日创建

然后是 data.table

library(bench)
library(dplyr, warn.conflicts = FALSE)
library(data.table, warn.conflicts = FALSE)
set.seed(123)

DT = data.table(x = rep(1:10, times = 1e5),
                y = sample(LETTERS[1:10], 10e5, replace = TRUE),
                z = rnorm(1e6))

DT[x > 7, mean(z), by = y]
#>     y            V1
#>  1: F -0.0056834238
#>  2: I -0.0016755202
#>  3: J  0.0066061660
#>  4: G -0.0034436348
#>  5: B -0.0070242788
#>  6: E -0.0070462070
#>  7: H  0.0005525803
#>  8: D -0.0043024627
#>  9: A -0.0033609302
#> 10: C  0.0029146372

bench::bench_process_memory()
#>  current      max 
#> 948.47MB   1.17GB

由reprex package (v0.3.0) 于 2020 年 4 月 22 日创建

因此,基本上 data.table 似乎使用了 dplyr 用于这种简单过滤+分组操作的几乎 两倍 内存。请注意,我实质上是在复制@Arun 建议here 的用例,它在 data.table 方面的内存效率会更高。 (data.table 仍然快很多。)

有什么想法,还是我只是遗漏了一些明显的东西?

附:顺便说一句,比较内存使用最终比最初看起来更复杂,因为 R 的标准内存分析工具(Rprofmem 和 co。)所有在 R 之外发生的ignore 操作(例如对 C++ 堆栈的调用)。幸运的是,bench 包现在提供了一个bench_process_memory() 函数,该函数还可以跟踪 R 的 GC 堆之外的内存,这就是我在这里使用它的原因。

sessionInfo()
#> R version 3.6.3 (2020-02-29)
#> Platform: x86_64-pc-linux-gnu (64-bit)
#> Running under: Arch Linux
#> 
#> Matrix products: default
#> BLAS/LAPACK: /usr/lib/libopenblas_haswellp-r0.3.9.so
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] 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.12.8 dplyr_0.8.99.9002 bench_1.1.1.9000 
#> 
#> loaded via a namespace (and not attached):
#>  [1] Rcpp_1.0.4.6      knitr_1.28        magrittr_1.5      tidyselect_1.0.0 
#>  [5] R6_2.4.1          rlang_0.4.5.9000  stringr_1.4.0     highr_0.8        
#>  [9] tools_3.6.3       xfun_0.13         htmltools_0.4.0   ellipsis_0.3.0   
#> [13] yaml_2.2.1        digest_0.6.25     tibble_3.0.1      lifecycle_0.2.0  
#> [17] crayon_1.3.4      purrr_0.3.4       vctrs_0.2.99.9011 glue_1.4.0       
#> [21] evaluate_0.14     rmarkdown_2.1     stringi_1.4.6     compiler_3.6.3   
#> [25] pillar_1.4.3      generics_0.0.2    pkgconfig_2.0.3

由reprex package (v0.3.0) 于 2020-04-22 创建

【问题讨论】:

我很高兴看到这个新的bench_process_memory!您可以在我的一张幻灯片 jangorecki.gitlab.io/r-talks/2019-06-18_Poznan_why-data.table/… 中找到有关内存使用的一些额外信息 你可以试试最近的 dplyr 吗? AFAIK 他们重写了 groupby 感谢@jangorecki,非常有帮助!首先回答后一个问题,是的,我正在使用最新的开发版 dplyr。话虽如此,我只是根据您的幻灯片建议使用cgmemtime 重新进行了内存评估,现在这些数字略微偏向于 data.table。我会相应地更新我的答案。 【参考方案1】:

更新:根据@jangorecki 的建议,我使用cgmemtime shell 实用程序重新进行了分析。数字更接近——即使启用了多线程——并且 data.table 现在将 dplyr w.r.t 挤到 .high-water RSS+CACHE 内存使用量。

dplyr

$ ./cgmemtime Rscript ~/mem-comp-dplyr.R
Child user:    0.526 s
Child sys :    0.033 s
Child wall:    0.455 s
Child high-water RSS                    :     128952 KiB
Recursive and acc. high-water RSS+CACHE :     118516 KiB

data.table

$ ./cgmemtime Rscript ~/mem-comp-dt.R
Child user:    0.510 s
Child sys :    0.056 s
Child wall:    0.464 s
Child high-water RSS                    :     129032 KiB
Recursive and acc. high-water RSS+CACHE :     118320 KiB

底线:在 R is complicated 内准确测量内存使用情况。

我会在下面留下我原来的答案,因为我认为它仍然有价值。

原始答案:

好的,所以在写这篇文章的过程中,我意识到 data.table 的默认多线程行为似乎是罪魁祸首。如果我重新运行后一个块,但这次轮到多线程,这两个结果更具可比性:

library(bench)
library(dplyr, warn.conflicts = FALSE)
library(data.table, warn.conflicts = FALSE)
set.seed(123)
setDTthreads(1) ## TURN OFF MULTITHREADING

DT = data.table(x = rep(1:10, times = 1e5),
                y = sample(LETTERS[1:10], 10e5, replace = TRUE),
                z = rnorm(1e6))

DT[x > 7, mean(z), by = y]
#>     y            V1
#>  1: F -0.0056834238
#>  2: I -0.0016755202
#>  3: J  0.0066061660
#>  4: G -0.0034436348
#>  5: B -0.0070242788
#>  6: E -0.0070462070
#>  7: H  0.0005525803
#>  8: D -0.0043024627
#>  9: A -0.0033609302
#> 10: C  0.0029146372

bench::bench_process_memory()
#> current     max 
#>   589MB   612MB

由reprex package (v0.3.0) 于 2020 年 4 月 22 日创建

不过,我很惊讶他们离得这么近。如果我尝试使用更大的数据集(尽管使用单线程),data.table 内存性能实际上会变得更差 - 这让我怀疑我仍然没有正确测量内存使用情况......

【讨论】:

以上是关于重新审视 data.table 与 dplyr 的内存使用的主要内容,如果未能解决你的问题,请参考以下文章

一个接一个地加载 Data.Table 和 dplyr 会出错

data.table 按行求和,平均值,最小值,最大值,如 dplyr?

使用 count()、aggregate()、data.table() 或 dplyr() 汇总数据(均值、标准差)

data.table vs dplyr:一个人可以做得很好,而另一个人不能或做得很差?

为啥 data.table 的 inner_join 行为不同?

data.table相当于dplyr :: filter_at