Пересмотр использования data.table и dplyr памяти - PullRequest
6 голосов
/ 23 апреля 2020

Я знаю, что data.table против dplyr сравнения - постоянный фаворит на 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

Создан в 2020-04-22 пакетом Представлять (v0.3.0 )

Затем 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

Создано в 2020-04-22 пакетом prepx (v0.3.0)

Итак, в основном data.table использует почти дважды память, для которой dplyr делает это простая операция фильтрации + группировки. Обратите внимание, что я, по сути, тиражирую сценарий использования, который @Arun предложил здесь будет намного более эффективным с точки зрения памяти на стороне data.table. ( data.table все же намного быстрее.)

Есть идеи, или я просто упускаю что-то очевидное?

PS В качестве сравнения, сравнение использования памяти в конечном итоге оказывается более сложным, чем кажется на первый взгляд, потому что стандартные инструменты профилирования памяти R (Rprofmem и соавторы) все игнорируют операций, которые происходят вне R (например, обращения к стеку C ++). К счастью, пакет bench теперь предоставляет функцию bench_process_memory(), которая также отслеживает память вне кучи R * G C, поэтому я использую ее здесь.

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

Создано в 2020-04-22 пакетом представ. (v0.3.0)

1 Ответ

4 голосов
/ 23 апреля 2020

ОБНОВЛЕНИЕ: Следуя предложению @ jangorecki, я переделал анализ, используя утилиту оболочки cgmemtime . Числа намного ближе - даже с включенной многопоточностью - и data.table теперь ограничивает использование памяти dplyr по сравнению с .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 является сложным .

Я оставлю свой оригинальный ответ ниже, потому что я думаю, что он все еще имеет значение.

ОРИГИНАЛЬНЫЙ ОТВЕТ:

Хорошо, так что в процессе написания этого я осознал, что многопоточность по умолчанию 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

Создано в 2020-04-22 с помощью Представить пакет (v0.3.0)

Тем не менее, я удивлен, что они так близко. Производительность памяти data.table фактически становится сравнительно хуже, если я пытаюсь использовать больший набор данных - несмотря на использование одного потока - что вызывает у меня подозрение, что я все еще неправильно измеряю использование памяти ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...