비슷한 작업을 한 줄에 모으는 습관이 있습니다. 내가 필터링해야하는 경우 예를 들어 a
, b
및 c
데이터 테이블에, 나는 하나에 함께 넣어 것이다 []
AND 연산과 함께. 어제, 나는 내 특별한 경우에 이것이 매우 느리고 체인 필터를 테스트 한 것으로 나타났습니다. 아래에 예제를 포함 시켰습니다.
먼저 난수 생성기를 시드하고 data.table을 로드 하고 더미 데이터 세트를 만듭니다.
# 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))
다음으로 메소드를 정의합니다. 첫 번째 접근 체인은 함께 필터링합니다. 두 번째는 필터를 함께 AND합니다.
# 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에서 생성 된 논리 벡터 사용).
보너스 질문
이 원칙은 일반적으로 데이터 테이블 작업에 적용됩니까? 모듈화 작업이 항상 더 나은 전략입니까?
답변
대부분의 대답은 다음과 같이 의견에 제시되어있다. “연쇄 방법” data.table
은이 경우 연쇄가 조건을 차례로 실행하므로 “연쇄 방법” 보다 빠르다. 각 단계는 크기를 줄이므로 다음 단계에서는 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
여기서 볼 수 있듯이이 경우 anding 접근 방식은 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