将%chin%用于自动索引data.table的子集字符列是否会提高速度?

Mat*_*ill 7 r data.table

TLDR:当使用更新版本的data.table使用自动索引时,使用%chin%在字符列上对data.table进行子集是否有任何好处?

在过去,使用%chin%data.table代替%in%对特征向量子集化时产生了显著加速.在较新版本中data.table,子设置时会在非键列上自动创建辅助索引.这些指数的创建和使用似乎使得之间存在任何速度差异,%chin%并且%in%无关紧要.

展望未来,是否存在使用数据%chin%子集的情况.表格仍将提高速度,或者我可以%in%在将来使用?


更新:关于PR#2494的对话:对复合查询更好的子集化优化似乎支持一种理解,即在data.table调用环境中进行求值时,执行方法%chin%已经从根本上改变了.

对于用于对表进行多次子集化的列的情况,通过自动索引会大幅提高性能,但是当仅使用一次时(因此不会从生成索引所花费的时间中获益),关闭自动索引有时会略微提高结果.

我会把这个开放几天,但为了后代,我可能会把它变成一个答案.


数据生成和基准测试

生成的数据由两个不平衡样本的随机排序组合组成:

  • 1000万个单字符串,26个可能的唯一值
  • 100万个四个字符串,456,976个可能的唯一值

这里的目的是代表非正态变量,这些变量由少数常见值支配,但具有许多不太常见的可能性.

library(data.table)
library(microbenchmark)
set.seed(1234)

## Create a vector of 1 million 4 character strings
## with 456,976 possible unique values 
DiverseSize <- 1e6
Diverse <- paste0(sample(LETTERS,DiverseSize,replace = TRUE),
                  sample(letters,DiverseSize,replace = TRUE),
                  sample(letters,DiverseSize,replace = TRUE),
                  sample(letters,DiverseSize,replace = TRUE))

## Create a vector of 10 million single character strings
## with 26 possible unique values
CommonSize  <- 1e7
Common <-  sample(LETTERS,CommonSize,replace = TRUE)

## Mix them into a data.table column, "x"
DT1 <- data.table(x = sample(c(Diverse,Common),size = CommonSize + DiverseSize, replace = FALSE))
## Make a deep copy to run independent comparisons
DT2 <- copy(DT1)
Run Code Online (Sandbox Code Playgroud)

比较%in%%chin%

当在data.table环境之外执行时,我们仍然可以通过使用来获得显着的加速%chin%.

microbenchmark(
  Outside_chin = length(which(DT1[["x"]] %chin% c("Matt"))),
  Outside_in   = length(which(DT2[["x"]] %in% c("Matt"))),
  times = 1
)
Run Code Online (Sandbox Code Playgroud)

...

Unit: milliseconds
         expr      min       lq     mean   median       uq      max neval
 Outside_chin 254.5967 254.5967 254.5967 254.5967 254.5967 254.5967     1
   Outside_in 476.2117 476.2117 476.2117 476.2117 476.2117 476.2117     1
Run Code Online (Sandbox Code Playgroud)

比较%in%%chin%自动索引

## Benchmarking -------
## Turn off Indices
options(datatable.auto.index = FALSE)
options(datatable.use.index = FALSE)

## Run without indices
DT2[x %chin% c("Matt"), .N]
DT1[x %in% c("Matt"), .N]

## Run Again
DT2[x %chin% c("Matt"), .N]
DT1[x %in% c("Matt"), .N]

options(datatable.auto.index = TRUE)
options(datatable.use.index = TRUE)

## First run builds indices and takes longer
DT2[x %chin% c("Matt"), .N]
DT1[x %in% c("Matt"), .N]

## Run again, benefiting from pre-built indices
DT2[x %chin% c("Matt"), .N]
DT1[x %in% c("Matt"), .N]
Run Code Online (Sandbox Code Playgroud)

分析结果

使用ProfVis分析每个表达式的运行时时,以下内容显而易见:

  1. 没有自动索引,%chin%速度更快,运行时间与第一次和第二次相似.
  2. 自动索引,运行时间基本相同是否%chin%%in%使用.
  3. 虽然自动索引使第一次执行稍慢,但第二次调用评估如此快,profvis甚至无法在10 ms采样间隔内始终捕获它

%chin %% in%comparison


我目前正在运行data.table版本1.10.5,建于2018-03-17 07:30:06 UTC.

Mat*_*ill 4

为了结束,在这里提供一个答案:我已经尽力浏览了 Github 上的相关提交,但肯定有可能我错过了一些更细微的差别。如果任何贡献者想要做出一些澄清,我很乐意在这里更新。

PR #2494:更好的复合查询子集优化似乎支持这样一种理解:在 data.table 调用环境中进行评估时,执行方法已经%chin%发生了根本性的改变。

  1. data.table在使用同一列多次对表进行子集化的情况下对a 进行子集设置时,自动索引可以显着提高性能,因此请坚持使用默认选项:options(datatable.auto.index = TRUE)options(datatable.use.index = TRUE)在这种情况下,使用%chin%不会带来任何性能提升。

  2. 如果性能至关重要,并且您知道您只会对给定列进行一次子设置(因此不会从生成索引所花费的时间中受益),则使用 和 关闭自动索引options(datatable.auto.index = FALSE)可以稍微加快结果。如果要执行重复的子集,您仍然可以像往常一样手动创建索引和键,但优化的负担将由用户来适当使用setkey()setindex()当自动索引关闭时,使用%chin%会比%in%.

  3. 当测试某个元素是否存在于调用环境之外的data.table字符向量中时,%chin%仍然比%in%