производительность data.table с использованием .SD с помощью - PullRequest
3 голосов
/ 24 апреля 2020

Я хочу отфильтровать большую data.table по группе. Я могу использовать .SD или .I, и, хотя я лично считаю, что первое намного легче читать, последнее намного быстрее / использует намного меньше памяти (несмотря на использование .SDcols).

В некоторой степени мне понятно почему. Для .I нам нужен вектор для каждой группы, а для .SD нам нужно целое data.table. Но я подумал, что, предоставив значимый аргумент .SDcol, я смог бы ускорить / сохранить некоторую память.

Тем не менее, тесты показывают, что подход .SD работает примерно в 60 раз медленнее и потребляет в 300 раз больше памяти. , Конечно, 4 столбца .SD data.table потребуется более чем в 4 раза больше вектора. Но в 60 раз медленнее и в 300 раз больше памяти? Может ли кто-нибудь просветить меня, почему подход .SD пожирает столько памяти и, следовательно, намного медленнее? Есть ли способ, как я мог ускорить заход на посадку .SD, чтобы быть быстрее, или это единственный вариант, чтобы вернуться к заходу на посадку .I?

Настройка данных

library(data.table)
## data set up

nr <- 1e6
nc <- 100
grp_perc <- .8
DT <- data.table(ids = sample(paste0("id", 
                                     seq(1, round(grp_perc * nr, 0))),
                              nr, TRUE))
cols <- paste("col", seq(1, nc), sep = "_")
DT[, (cols) := replicate(nc, sample(nr), simplify = FALSE)]

Тесты

results <- bench::mark(.I = DT[DT[, .(row_id = .I[which.min(col_1)]), 
                                  by = ids]$row_id, c("ids", cols[1:3]), with = FALSE],
                       .SD = DT[, .SD[which.min(col_1)], 
                                by = ids, .SDcols = cols[1:3]], 
                       iterations = 1, filter_gc = FALSE)

summary(results)
# A tibble: 2 x 13
  expression     min  median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time result         memory         time   gc       
  <bch:expr> <bch:t> <bch:t>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> <list>         <list>         <list> <list>   
1 .I           2.64s   2.64s   0.378      34.4MB    0         1     0      2.64s <df[,4] [571,~ <df[,3] [1,41~ <bch:~ <tibble ~
2 .SD          2.73m   2.73m   0.00612     9.1GB    0.342     1    56      2.73m <df[,4] [571,~ <df[,3] [2,40~ <bch:~ <tibble ~

Ответы [ 2 ]

3 голосов
/ 25 апреля 2020

Вот подход, который быстрее чем .I для этого конкретного примера. Обратите внимание, что это также меняет порядок, который может быть нежелателен для вас.

DT[order(col_1), .SD[1L], by = ids, .SDcols = cols[1:3]]

Как упоминает @Ian Campbell, это проблема Github. Хорошей новостью является то, что есть некоторые оптимизации, одна из которых .SD[1L]. Оптимизация заключается в том, что поднабор выполняется всего за C, что делает его очень быстрым.

Вот тесты, которые включают решение @ sindri_baldur, но удаляют вашу первоначальную попытку .SD - я не хотел ждать 3 минуты:).

# A tibble: 3 x 13
  expression    min median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time
  <bch:expr> <bch:> <bch:>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm>
1 .I          4.54s  4.54s    0.220       30MB    0.880     1     4      4.54s
2 self_join  11.32s 11.32s    0.0883    76.3MB    0         1     0     11.32s
3 use_order   3.55s  3.55s    0.282     58.3MB    0         1     0      3.55s

## show that it's equal but re-ordered:
all.equal(DT[DT[, .(row_id = .I[which.min(col_1)]), 
                by = ids]$row_id, c("ids", cols[1:3]), with = FALSE][order(col_1)],
          DT[order(col_1), .SD[1L], by = ids, .SDcols = cols[1:3]])

## [1] TRUE
3 голосов
/ 24 апреля 2020

Вот более быстрый способ, который все еще использует .SD.

DT[DT[, .(col_1 = min(col_1)), by = ids], 
   on = .(ids, col_1), 
   .SD, .SDcols = c("ids", cols[1:3])]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...