Работая с пакетом data.table
R, я заметил очень высокую загрузку процессора при запуске простого цикла for
, который бы поднастраивал набор данных, используя значения из другого data.table
.Когда я говорю «высокий уровень использования», я имею в виду 100% всех доступных потоков за все время выполнения цикла.
Интересно то, что использование объекта data.frame
для одного и того же процесса занимает в 10 раз меньше времени для того жевыход.И только с одним из ядер на 100%.
Вот мой, мы надеемся, воспроизводимый пример:
chr = c(rep(1, 1000), rep(2, 1000), rep(3, 1000), rep(3,1000))
start = rep(seq(from =1, to = 100000, by=100), 4)
end = start + 100
df1 <- data.frame(chr=chr, start=start, end=end)
df2 <- rbind(df1,df1,df1,df1,df1)
dt1 <- data.table::data.table(df1)
dt2 <- data.table::data.table(df2)
test1 <- list()
test2 <- list()
#loop subsetting a data.frame
system.time(
for (i in 1:nrow(df2)) {
no.dim <- dim(df1[df1$chr == df2[i, 'chr'] & df1$start >= df2[i, 'start'] & df1$end <= df2[i, 'end'], ])[1]
test1[i] <- no.dim
})
# loop subsetting a data.table using data.table syntax
system.time(
for (i in 1:nrow(dt2)) {
no.dim <- dim(dt1[chr == dt2[i, chr] & start >= dt2[i, start] & end <= dt2[i, end], ])[1]
test2[i] <- no.dim
})
# is the output the same
identical(test1, test2)
И это вывод:
> #loop subsetting a data.frame
> system.time(
+ for (i in 1:nrow(df2)) {
+ no.dim <- dim(df1[df1$chr == df2[i, 'chr'] & df1$start >= df2[i, 'start'] & df1$end <= df2[i, 'end'], ])[1]
+ test1[i] <- no.dim
+ })
user system elapsed
2.607 0.004 2.612
>
> # loop subsetting a data.table using data.table syntax
> system.time(
+ for (i in 1:nrow(dt2)) {
+ no.dim <- dim(dt1[chr == dt2[i, chr] & start >= dt2[i, start] & end <= dt2[i, end], ])[1]
+ test2[i] <- no.dim
+ })
user system elapsed
192.632 0.152 24.398
>
> # is the output the same
> identical(test1, test2)
[1] TRUE
Теперь,Я знаю, что, вероятно, есть несколько более эффективных и эффективных способов выполнить одну и ту же задачу, и что я, вероятно, не делаю это способом data.table
.Но допустим, по какой-то причине у вас был скрипт, использующий объекты data.frame, и вы хотели быстро переписать его, чтобы вместо него использовать data.table
.Приведенный выше подход кажется вполне правдоподобным.
Может ли кто-нибудь воспроизвести такую же ситуацию с замедлением и высокой загрузкой процессора?Это как-то поправимо, сохраняя более или менее тот же процесс подмножества, или его нужно полностью переписать, чтобы эффективно использовать на data.table
?
PS: только что протестировал его на машине с Windows и в потокеиспользование было нормальным (один поток работал на 100%), но все еще медленнее.Протестировано на системе, аналогичной моей, дало те же результаты, что и выше.
R version 3.5.1 (2018-07-02)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.10
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.8.0
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.8.0
locale:
[1] LC_CTYPE=C LC_NUMERIC=C LC_TIME=C LC_COLLATE=C
[5] LC_MONETARY=C LC_MESSAGES=C LC_PAPER=et_EE.UTF-8 LC_NAME=C
[9] LC_ADDRESS=C LC_TELEPHONE=C LC_MEASUREMENT=C LC_IDENTIFICATION=C
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] data.table_1.12.0
loaded via a namespace (and not attached):
[1] compiler_3.5.1 assertthat_0.2.0 cli_1.0.1 tools_3.5.1 pillar_1.3.1
[6] rstudioapi_0.9.0 tibble_2.0.0 crayon_1.3.4 utf8_1.1.4 fansi_0.4.0
[11] pkgconfig_2.0.2 rlang_0.3.1
РЕДАКТИРОВАТЬ:
Спасибо всем за их комментарии.Похоже, что проблема замедления связана с накладными расходами [.data.table
, как подробно описано в @Hugh.Та же проблема была упомянута здесь эффективное подмножество data.table с индексами «больше, чем меньше» с использованием , на что указывает @denis.
Исправление, предложенное @Frank, хотя оно действительно эффективно и дает аналогичные результаты, изменяет поведение процесса, полностью удаляя цикл, и добавляет, возможно, нежелательный столбец в исходный набор данных.
EDIT.1:
После моего первого редактирования @Frank добавил еще один подход, который состоит в вычислении столбца списка с использованием синтаксиса data.table.Хотя это довольно опрятно, я должен признать, что мне нужно было время, чтобы понять, что происходит.Я решил, что это просто вычисление lm () в начале и конце столбца подмножества data.table, поэтому я попытался воспроизвести результаты, используя цикл for и data.frames.Время:
> system.time({res <- dt1[dt2, on=.(chr, start >= start, end <= end), .(n = .N, my_lm = list(lm(x.start ~ x.end))), by=.EACHI][, .(n, my_lm)]; res <- as.list(res$my_lm)})
user system elapsed
11.538 0.003 11.336
>
> test_new <- list()
> system.time(
+ for (i in 1:20000) {
+ df_new <- df1[df1$chr == df2$chr[i] & df1$start >= df2$start[i] & df1$end <= df2$end[i],]
+ test_new[[i]] <- lm(df_new$start ~ df_new$end)
+ })
user system elapsed
12.377 0.048 12.425
>
Пока у вас есть функция узкого места как lm (), вам лучше (для контроля и читабельности) использовать базовый цикл for, но использовать data.frames.