筛选数据表时,链接比ANDing的性能优势


12

我习惯于将相似的任务集中到一行中。例如,如果我需要过滤ab以及c在数据表中,我把它们放在一起在一个[]与AND运算。昨天,我注意到在我的特定情况下,它的运行速度非常慢,并且经过了测试,却没有测试链接过滤器。我在下面提供了一个示例。

首先,我为随机数生成器添加种子,加载,并创建一个虚拟数据集。

# Set RNG seed
set.seed(-1)

# Load libraries
library(data.table)

# Create data table
dt <- data.table(a = sample(1:1000, 1e7, replace = TRUE),
                 b = sample(1:1000, 1e7, replace = TRUE),
                 c = sample(1:1000, 1e7, replace = TRUE),
                 d = runif(1e7))

接下来,我定义我的方法。第一种方法将过滤器链接在一起。第二个将过滤器与在一起。

# Chaining method
chain_filter <- function(){
  dt[a %between% c(1, 10)
     ][b %between% c(100, 110)
       ][c %between% c(750, 760)]
}

# Anding method
and_filter <- function(){
  dt[a %between% c(1, 10) & b %between% c(100, 110) & c %between% c(750, 760)]
}

在这里,我检查它们是否给出相同的结果。

# Check both give same result
identical(chain_filter(), and_filter())
#> [1] TRUE

最后,我对它们进行基准测试。

# Benchmark
microbenchmark::microbenchmark(chain_filter(), and_filter())
#> Unit: milliseconds
#>            expr      min        lq      mean    median        uq       max
#>  chain_filter() 25.17734  31.24489  39.44092  37.53919  43.51588  78.12492
#>    and_filter() 92.66411 112.06136 130.92834 127.64009 149.17320 206.61777
#>  neval cld
#>    100  a 
#>    100   b

reprex软件包(v0.3.0)创建于2019-10-25

在这种情况下,链接可将运行时间减少约70%。为什么会这样呢?我的意思是,数据表的底层是什么?我没有看到任何关于禁止使用的警告&,所以我惊讶地发现两者之间的差异如此之大。在这两种情况下,他们评估的条件相同,因此应该没有区别。在AND情况下,&是一个快速运算符,然后它只需过滤数据表一次(即,使用AND产生的逻辑向量),而不是在链接情况下过滤3次。

奖金问题

该原则通常适用于数据表操作吗?模块化任务始终是更好的策略吗?


1
我同理这一观察,也想知道同样的事情。以我的经验,在整个常规操作中都观察到链接速度的提高。
JDG

9
尽管data.tavle确实对此类情况进行了一些优化(仅此一项是一项壮举,并且相对于基准R而言是一个很大的改进!),通常A&B&C&D将在合并结果和过滤之前评估所有N个逻辑条件时间。而通过链接第二,第三和第四逻辑调用仅被评估n次(其中n <= N是每个条件之后剩余的行数)
MichaelChirico

@MichaelChirico哇。真令人惊讶!我不知道为什么,但是我只是假设它会像C ++短路一样工作
duckmayr

跟随@MichaelChirico的评论,您可以base通过执行以下操作对vectors 进行类似的观察: chain_vec <- function() { x <- which(a < .001); x[which(b[x] > .999)] }and_vec <- function() { which(a < .001 & b > .999) }。(其中ab是长度相同的矢量runif-我用于n = 1e7这些临界值)。
ClancyStats

@MichaelChirico啊,我知道了。因此,最大的区别在于,在链的每个步骤中,数据表都大大缩小了,因此可以更快地评估条件并进行过滤?这就说得通了。感谢您的见解!
Lyngbakr

Answers:


8

多数情况下,答案是在无铅评论中给出的:data.table在这种情况下,“链接方法” 比“ anding方法”更快,因为链接会依次运行条件。随着每个步骤的减小data.table,对下一个步骤的评估就更少了。“ Anding”每次都会评估完整尺寸数据的条件。

我们可以举一个例子来说明这一点:当各个步骤不减小的大小时data.table(即,两个条件的检查条件都相同):

chain_filter <- function(){
  dt[a %between% c(1, 1000) # runs evaluation but does not filter out cases
     ][b %between% c(1, 1000)
       ][c %between% c(750, 760)]
}

# Anding method
and_filter <- function(){
  dt[a %between% c(1, 1000) & b %between% c(1, 1000) & c %between% c(750, 760)]
}

使用与bench程序包相同的数据,该程序包将自动检查结果是否相同:

res <- bench::mark(
  chain = chain_filter(),
  and = and_filter()
)
summary(res)
#> # A tibble: 2 x 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 chain         299ms    307ms      3.26     691MB     9.78
#> 2 and           123ms    142ms      7.18     231MB     5.39
summary(res, relative = TRUE)
#> # A tibble: 2 x 6
#>   expression   min median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <dbl>  <dbl>     <dbl>     <dbl>    <dbl>
#> 1 chain       2.43   2.16      1         2.99     1.82
#> 2 and         1      1         2.20      1        1

如您所见,在这种情况下,“与” 运算的速度快了2.43倍。这意味着链接实际上会增加一些开销,这表明通常ANDing应该更快。如果条件正在data.table逐步缩小,则除外。从理论上讲,链接方法甚至可能更慢(甚至不考虑开销),即条件是否会增加数据的大小。但是实际上我认为这是不可能的,因为不允许使用逻辑向量的循环data.table。我认为这可以回答您的奖金问题。

为了进行比较,我机器上的原始功能包括bench

res <- bench::mark(
  chain = chain_filter_original(),
  and = and_filter_original()
)
summary(res)
#> # A tibble: 2 x 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 chain        29.6ms   30.2ms     28.5     79.5MB     7.60
#> 2 and         125.5ms  136.7ms      7.32   228.9MB     7.32
summary(res, relative = TRUE)
#> # A tibble: 2 x 6
#>   expression   min median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <dbl>  <dbl>     <dbl>     <dbl>    <dbl>
#> 1 chain       1      1         3.89      1        1.04
#> 2 and         4.25   4.52      1         2.88     1
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.