1 R 기준성능 평가 팩키지1

본인이 작성한 코드의 성능을 알고자 하는 것은 어찌 생각하면 당연할수도 있다. 실제 코드를 다시 들여다 볼 때는 아마도 작성한 코드가 너무 느리게 실행될 때다. 하지만, R 코드 성능을 측정하는 관련된 방법이 너무 다양하게 존재하여 실제 현업에 적용할 때 어려운 점이 생긴다.

  • Sys.time
  • tictoc
  • bench
  • system.time
  • rbenchmark
  • microbenchmark

2 Sys.time(), system.time()

기본적인 사용법은 Sys.time()으로 현재 시간을 찍고 나서, 코드를 실행하고 나서, 종료시점에 다시 Sys.time()을 찍고 두 시간 차이를 구하는 방식이다.

start_time <- Sys.time()
Sys.sleep(3) # 3초 동안 쿨쿨 ~~~
end_time <- Sys.time()

end_time - start_time
Time difference of 3.063373 secs

또다른 방식은 system.time()을 활용하는 것인데 앞선 Sys.time()을 사용하는 것과 비교하여 어느 지점에서 시간이 더 많이 사용되는지 개발자가 알 수 있게 추가적인 정보를 제공한다.

system.time(Sys.sleep(3))
   user  system elapsed 
   0.00    0.00    3.07 

사용자, 시스템, elapsed로 전체 시간을 나누어서 실행시간에 대한 정보를 제공하고 있다.2

  • 사용자 혹은 user: User CPU time은 현재 R 세션에서 사용한 CPU 시간.
  • 시스템 혹은 system: System CPU time은 커널(운영체제)에서 사용한 CPU 시간. 운영체제에서 사용한 시간은 프로세스 기동, 입출력 작업 등 많은 프로세스가 공통적으로 수행하는 작업이 포함된다. 따라서 운영체제가 다르면 수행시간도 차이가 나는 것도 명약관화하다.
  • elapsed: R작업과 운영체제 시스템 총 작업경과 시간.

3 tictoc, microbenchmark, rbenchmark 팩키지 비교3

tictoc, microbenchmark, rbenchmark 팩키지는 R코드 속도를 측정하고자하는 목적으로 개발된 것이다. 목적은 동일하지만 다소 차이가 있다.

피보나치 순열을 재귀를 사용해서 구현한 R코드와 Rcpp코드의 성능을 비교한다. Rcpp 저자인 Dirk Eddelbuettel 박사가 EARL 2015에서 스택오버플로우 예제를 참조하여 작성한 예제다.

\[ f(n) = \begin{cases} n, & n < 2 \mbox{ 일 때}\\ f(n-1) + f(n-2), & n \geq 2 \mbox{ 일 때} \end{cases}\]

피보나치 수열을 R코드로 구현한 후 sapply 함수로 0 에서 10까지 숫자를 피보나치 함수 f에 넣어 계산한다.

library(tidyverse)
#-------------------------------------------------------------------------
# 1. 순수한 R 코드
#-------------------------------------------------------------------------

calc_fibo <- function(n) {
  if ( n < 2 ) return(n)
    return( calc_fibo(n-1) + calc_fibo(n-2) )
}

map_dbl(1:10, calc_fibo)
 [1]  1  1  2  3  5  8 13 21 34 55

3.1 tictoc 속도 측정

tic 함수와 tok 함수를 통해 tictoc 사이 소요되는 시간을 측정한다.

# devtools::install_github("collectivemedia/tictoc")
library(tictoc)

tic("전체시간")

tic("피보나치 10")
calc_fibo(10)
[1] 55
toc()
피보나치 10: 0 sec elapsed
tic("피보나치 15")
calc_fibo(15)
[1] 610
toc()
피보나치 15: 0 sec elapsed
tic("피보나치 20")
calc_fibo(20)
[1] 6765
toc()
피보나치 20: 0.01 sec elapsed
toc()
전체시간: 0.01 sec elapsed

3.2 rbenchmark 속도 측정

rbenchmark 팩키지는 펄(Perl) 벤치마크 모듈에 영감을 받아 제작되었다. system.time을 감싼 함수 하나로 구성된 팩키지다. system.time과 비교하여 반복수와 명시적으로 user.self, sys.self로 명칭을 명확히 했고, relative를 통해 상대적인 시간도 명기했다.

사용법은 benchmark 함수에 기준성능을 평가할 함수를 넣어주고, columns= c("test", "replications", "elapsed", "relative") 출력하려고 하는 인자를 넣어준다.

rbenchmark 팩키지를 사용해서 10, 15, 20일 경우를 기준성능을 상호비교한다.

# install.packages("rbenchmark")
library(rbenchmark)

benchmark(calc_fibo(10), calc_fibo(15), calc_fibo(20), columns= c("test", "replications", "elapsed", "relative"))
           test replications elapsed relative
1 calc_fibo(10)          100    0.01        1
2 calc_fibo(15)          100    0.09        9
3 calc_fibo(20)          100    1.05      105

피보나치 순열 10일 때 보다 20일 때, 상대적으로 131배나 많은 시간이 소요된 것이 확인된다. 이제 C/C++ 즉, Rcpp로 구현한 성능을 살펴본다.

3.3 microbenchmark 속도 측정

rbenchmark 팩키지 benchmark() 함수와 유사하지만, microbenchmark()함수는 ggplotautoplot과 함께 사용하면 정말 편리하다.

#devtools::install_github("olafmersmann/microbenchmarkCore")
#devtools::install_github("olafmersmann/microbenchmark")
library(microbenchmark)

mb_fibo <- microbenchmark("f10" = { calc_fibo(10) },
                      "f15" = { calc_fibo(15) },
                      "f20" = { calc_fibo(20) }
)

mb_fibo
Unit: microseconds
 expr    min      lq      mean   median       uq     max neval cld
  f10   61.9   64.70    79.983    66.75    80.55   151.7   100 a  
  f15  694.1  738.90   921.443   772.45   938.60  4570.7   100  b 
  f20 8034.4 9025.15 10695.562 10326.40 11822.25 18646.8   100   c
autoplot(mb_fibo)

3.4 bench 속도 측정

bench: High Precision Timing of R Expressions가 R 표현식에 대한 정확한 시간 측정에 사용되는 새로운 벤치마킹 속도 측정 팩키지로 ggplot과 결합하여 후속 작업도 깔끔하다.

library(bench)

fibo_10_bnch <- bench::mark(
  fibo_10 = calc_fibo(10)
)

fibo_15_bnch <- bench::mark(
  fibo_15 = calc_fibo(15)
)

fibo_20_bnch <- bench::mark(
  fibo_20 = calc_fibo(20)
)

# fibo_df <- bind_rows(fibo_10_bnch, fibo_15_bnch) %>% 
#   bind_rows(fibo_20_bnch)
# 에러: Can't combine `expression` <bench_expr> and `expression` <bench_expr>.
# x Some attributes are incompatible.
# i The author of the class should implement vctrs methods.
# i See <https://vctrs.r-lib.org/reference/faq-error-incompatible-attributes.html>.
# Run `rlang::last_error()` to see where the error occurred.

autoplot(fibo_20_bnch)

4 Rcpp 코드 구현

피보나치 구현하는 코드를 C/C++로 구현한 후에 Rcpp::cppFunction 내부에 C/C++코드를 넣어주고 실행시키면 된다. 전통적으로 재귀를 사용한 피보나치 순열 구현은 성능이 좋지 않은 것으로 유명한데, C/C++로 구현하여 Rcpp 팩키지로 실행한 것을 보면 450배 순수 R코드에 비해 성능이 월등한 것을 알 수 있다.

library(Rcpp)

Rcpp::cppFunction(
  "int calc_fibo_c(int n) {
      if (n < 2) return(n);
        return( calc_fibo_c(n-1) + calc_fibo_c(n-2) );
  }")

Rcpp_fibo <- microbenchmark("Rcpp"  = { calc_fibo_c(25) },
                            "RCode" = { calc_fibo(25) }
)

Rcpp_fibo
autoplot(Rcpp_fibo)

윈도우즈 Rcpp 설치

Rcpp를 윈도우즈에 설치할 경우 Rtools가 설치되어야 한다. 그리고 Rtools를 윈도우 사용자경로에 추가해야 한다.

Sys.getenv("PATH") %>% str_split(";") %>% .[[1]] 명령어를 실행하여 RRtools가 경로명에서 확인되어야 한다.

path_info <- Sys.getenv("PATH") %>% str_split(";") %>% .[[1]]

path_info[str_detect(path_info, "rtools")]
[1] "C:\\rtools40\\mingw64\\bin"

5 기준정보 벤치마크 시각화

microbenchmark 팩키지에 시각화하는 기능이 내장되어 있다. boxplot 등 기본 그래픽 기능을 활용하여 상대적인 비교를 시각적으로 식별하는데 도움을 주고 있다. 또한, ggplot2의 autoplot() 함수를 활용하면 상자그림이 갖고 있는 단점도 상당부분 보완할 수 있다.

 

데이터 과학자 이광춘 저작

kwangchun.lee.7@gmail.com