data.table 使用 .SD 和 by 的性能
data.table performance using .SD with by
我想按组筛选大型 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 ~
这是一种仍然使用 .SD 的更快方法。
DT[DT[, .(col_1 = min(col_1)), by = ids],
on = .(ids, col_1),
.SD, .SDcols = c("ids", cols[1:3])]
对于此特定示例,这是一种比 .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
我想按组筛选大型 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 ~
这是一种仍然使用 .SD 的更快方法。
DT[DT[, .(col_1 = min(col_1)), by = ids],
on = .(ids, col_1),
.SD, .SDcols = c("ids", cols[1:3])]
对于此特定示例,这是一种比 .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