[R] R에서 실행 시간(running time)을 계산하는 5가지 방법

R-bloggers에서 흥미로운 컨텐츠를 찾아 소개하는 포스트 입니다.
이번 포스트에서는 'R에서 실행 시간(running time)을 계산하는 5가지 방법'을 소개합니다.

개요

R에서 실행 시간을 측정하는 방법은 크게 2가지로, 시스템 함수를 이용하는 방법외부 패키지를 이용하는 방법이 있습니다. 아래에서는 두 분류에 포함되는 총 5가지 함수 및 패키지의 사용방법을 소개하며 마지막엔 간략한 결론을 제시합니다.

1. Using Sys.time

Sys.time()은 시스템 함수로 현재 시스템 시간을 출력하는 함수이며, 코드 청크[각주:1]의 시작과 끝의 시간 차이를 측정하여 측정 할 수 있습니다.

사용법


01 | start_time <- Sys.time() # start_time에 시작시간 입력
02 | <---측정이 필요한 함수/명령문 입력--->
03 | end_time <- Sys.time() # end_time에 종료시간 입력
04 | end_time - start_time # 시간과 끝 시간 차이 계산


사용 예시

sleep_for_a_minute <- function() { Sys.sleep(60) } # 60초 딜레이 발생 함수 

start_time <- Sys.time()    # 시작시간 입력
sleep_for_a_minute()    # 함수 실행
end_time <- Sys.time()  # 종료시간 입력

end_time - start_time   # 시간과 끝 시간 차이 계산
## Time difference of 1.024646 mins

2. Library tictoc

tictoc 패키지는 Sys.time과 같이 실행 시간을 측정하는데 사용할 수 있으며, 전반적으로 Sys.time보다 사용법이 훨씬 더 편리합니다.
시간 측정에는 tic()함수와 toc()함수를 사용하며, 두 함수 사이에 소요되는 시간이 출력됩니다.

사용법


01 | tic("sleeping") # 시작 지점(시간) 기록
02 | <---측정이 필요한 함수/명령문 입력--->
03 | toc() # 종료 지점(시간) 기록 - 프로세스 실행 시간 출력(tic에 입력된 텍스트가 있다면 같이 출력됨)


2.1 단일 코드 청크 시간 측정

단일 코드 실행의 경우 tic()toc() 사이에 시간 측정할 함수 or 명령문을 입력해 주시면 됩니다.

library(tictoc)

tic("sleeping")         # 시작
print("falling asleep...")
sleep_for_a_minute()    # 함수 실행
print("...waking up")
toc()                    # 종료

## [1] "falling asleep..."
## [1] "...waking up"
## sleeping: 60.026 sec elapsed

2.2 복합 코드 중첩 시간 측정

실행 코드가 중첩되어 사용되는 경우에도 측정 가능하며, 실행 시간 계산은 후입 선출법(LIFO, Last-In-First-Out)로 처리 됩니다. 이해를 돕기 위해 하단 코드에 주석을 기재해 놓았습니다.

tic("total")            # tic1 시작
tic("data generation")  # tic2 시작
X <- matrix(rnorm(50000*1000), 50000, 1000)
b <- sample(1:1000, 1000)
y <- runif(1) + X %*% b + rnorm(50000)
toc()                   # tic2 종료
tic("model fitting")    # tic3 시작
model <- lm(y ~ X)
toc()                   # tic3 종료
toc()                   # tic1 종료

## data generation: 3.792 sec elapsed
## model fitting: 39.278 sec elapsed
## total: 43.071 sec elapsed

3. Using system.time

system.time()은 하나의 표현식을 계산하는데 사용 가능합니다. 예를 들어, 위에서 정의한 sleep_for_a_minute 함수의 실행시간을 측정한다면 아래와 같이 사용할 수 있습니다.

사용법


01 | system.time({
02 | <---측정이 필요한 함수/명령문 입력--->
03 | })


사용 예시

system.time({ sleep_for_a_minute() })
##   user  system elapsed
##  0.004   0.000  60.051

위 결과에서 user, system, elapsed 출력 값 중 어느 결과를 보면되는지 명확하지 않습니다. 우리는 elasped가 function sleep_for_a_minute 실행 시간을 나타낸다고 예상할 수 있지만, 함수의 내용을 모르는 경우를 위해 출력값에 대한 이해를 명확히 할 필요가 있습니다.

systme.time() 출력 결과에 대하여 William Dunlap은 R-Help Mailing List 에 좋은 설명글을 올렸습니다.

User CPU time은 현재 프로세스에서 사용한 CPU 시간을 제공하고, System CPU time은 현재 프로세스를 대신하여 커널(운영 체제)에서 사용한 CPU 시간을 나타냅니다. 운영 체제는 파일 열기, 입력 또는 출력 수행, 다른 프로세스 시작 및 시스템 시간을 보는 등 많은 프로세스가 공유해야하는 리소스가 포함된 작업을 의미하며 서로 다른 운영 체제는 각각 다른 작업이 수행 됩니다.


아래 설명을 추가로 읽어보시면 이해하는데 좀 더 도움이 될 것 같습니다.

system.time( )이 출력하는 수행 시간은 user time, system time, elapsed time으로 구분된다. 이 중 elapsed time이 가장 알기 쉬운 개념이다. 이 시간은 코드의 총 소요 시간으로, 코드를 시작한 직후부터 코드 수행이 끝날 때까지의 시간을 초시계로 쟀을 때 얼마나 걸렸는지를 나타낸다. 통상적으로 이야기하는 코드 수행 시간이 이에 해당한다.
user time, system timeelapsed time을 구성하는 요소로, 각각 프로그램 코드 자체를 수행하는 데 걸린 시간과 프로그램이 운영체제의 명령을 호출했다면 그 때 운영체제가 명령을 수행하는 데 걸린 시간을 의미한다.

  • 출처 : [책] R을 이용한 데이터 처리 & 분석 실무 - 명령문 실행 시간 측정

4. Library rbenchmark

R 패키지 rbenchmark를 설명한 문서에서는 benchmark 함수를 'system.time() 을 간단하게 포장한 것'이라고 설명하고 있습니다. 그러나 system.time에 비해 많은 편의성을 제공합니다. 예를 들어, benchmark 다수의 식에 여러번의 복제를 지정하는데에 단 한 번의 호출만 필요합니다. 또한 반환된 결과는 데이터 프레임(data.frame)으로 편리하게 구성됩니다.

아래는 3가지 방법을 사용하여 선형 회귀 계수를 계산하여 비교하는 예제 입니다.

  1. lm
  2. the Moore-Penrose pseudoinverse
  3. the Moore-Penrose pseudoinverse but without explicit matrix inverses

사용법


01 | benchmark("테스트명1" = {
02 | <---측정이 필요한 함수/명령문(1) 입력--->
03 | },
04 | "테스트명2" = {
05 | <---측정이 필요한 함수/명령문(2) 입력--->
06 | },
07 | "테스트명3" = {
08 | <---측정이 필요한 함수/명령문(3) 입력--->
09 | },
10 | replications = 반복횟수,
11 | columns = c("출력 컬럼", ... ))


사용 예시

library(rbenchmark)

benchmark("lm" = {
            X <- matrix(rnorm(1000), 100, 10)
            y <- X %*% sample(1:10, 10) + rnorm(100)
            b <- lm(y ~ X + 0)$coef
          },
          "pseudoinverse" = {
            X <- matrix(rnorm(1000), 100, 10)
            y <- X %*% sample(1:10, 10) + rnorm(100)
            b <- solve(t(X) %*% X) %*% t(X) %*% y
          },
          "linear system" = {
            X <- matrix(rnorm(1000), 100, 10)
            y <- X %*% sample(1:10, 10) + rnorm(100)
            b <- solve(t(X) %*% X, t(X) %*% y)
          },
          replications = 1000,
          columns = c("test", "replications", "elapsed",
                      "relative", "user.self", "sys.self"))

##            test replications elapsed relative user.self sys.self
## 3 linear system         1000   0.167    1.000     0.208    0.240
## 1            lm         1000   0.930    5.569     0.952    0.212
## 2 pseudoinverse         1000   0.240    1.437     0.332    0.612

여기서, elapsed, user.self, sys.selfsystem.time에서 설명과 동일하며, relative는 단순히 빠르기를 테스트하는 시간의 비율입니다. 위 예제에서는 lm이 가장 느리게 나왔습니다.

5. Library microbenchmark

rbenchmark 패키지의 benchmark 함수와 마찬가지로, microbenchmark 함수를 사용하여 여러 R코드 청크 실행 시간을 비교할 수 있으며 더 많은 편의성과 추가 기능을 제공하고 있습니다.

특히 microbenchmark의 사용자 지정 함수를 사용하여 계산된 실행 시간을 자동으로 검증하는 기능은 유용한 기능 중 하나 입니다. 아래에서는 선형 모델의 계수 계수 벡터를 계산하는 3가지 방법을 비교합니다.

사용법


01 | microbenchmark("테스트명1" = {
02 | <---측정이 필요한 함수/명령문(1) 입력--->
03 | },
04 | "테스트명2" = {
05 | <---측정이 필요한 함수/명령문(2) 입력--->
06 | },
07 | "테스트명3" = {
08 | <---측정이 필요한 함수/명령문(3) 입력--->
09 | },
10 | check = 사용자 검증 함수)


사용예시

library(microbenchmark)

set.seed(2017)
n <- 10000
p <- 100
X <- matrix(rnorm(n*p), n, p)
y <- X %*% rnorm(p) + rnorm(100)

check_for_equal_coefs <- function(values) {
  tol <- 1e-12
  max_error <- max(c(abs(values[[1]] - values[[2]]),
                     abs(values[[2]] - values[[3]]),
                     abs(values[[1]] - values[[3]])))
  max_error < tol
}

mbm <- microbenchmark("lm" = { b <- lm(y ~ X + 0)$coef },
               "pseudoinverse" = {
                 b <- solve(t(X) %*% X) %*% t(X) %*% y
               },
               "linear system" = {
                 b <- solve(t(X) %*% X, t(X) %*% y)
               },
               check = check_for_equal_coefs)

mbm

## Unit: milliseconds
##           expr      min        lq      mean    median        uq      max neval cld
##            lm 96.12717 124.43298 150.72674 135.12729 188.32154 236.4910   100   c
##  pseudoinverse 26.61816  28.81151  53.32246  30.69587  80.61303 145.0489   100  b
##  linear system 16.70331  18.58778  35.14599  19.48467  22.69537 138.6660   100 a

함수 내 파라미터 check를 사용하여 3가지 방법 결과에 대한 동등성을 검증(최대 오류: 에러의 최대 값이 1e-12(매우 작은 수)보다 큰가)을 수행합니다. 만약, 결과가 같지않다면 에러 메시지를 출력합니다.

또 다른 큰 특징은 ggplot2 플로팅 과의 통합입니다.

library(ggplot2)
autoplot(mbm)

5 ways to measure running time of r code
▲ Microbenchmark results plot (R-bloggers: 5 ways to measure running time of r code)

결론

위에서 소개드린 'R 실행 시간(running time) 측정 방법'은 사용성이 높은 예제 위주로 설명드렸기에 완벽하다고 볼 수는 없습니다. 그러나 개인적인 결론을 내려본다면 아래와 같습니다.

  • tictoc 패키지 뿐만 아니라 Sys.time 함수는 복잡한 알고리즘(중첩된)의 실행 시간을 측정할 때 사용할 수 있습니다. 그러나 개인적으로는 tictoc 패키지를 선호합니다.

  • microbenchmark가 실행 시간 측정 이외에 다른 유형의 측정 값을 반환하는 것을 확인할 수 있었으며, 개인적으로 대부분의 상황에서의 microbenchmark 측정은 보다 실질적인 의미가 있다고 생각합니다. 개인적인 지식 범위에서는 microbenchmark가 시각화 기능이 내장된 유일한 벤치마킹 패키지로 활용도 측면에 우수성이 있다고 생각됩니다.

위와 같은 이유들로, 저는 앞으로 microbenchmarktictoc을 사용할 예정입니다. :)

관련 링크

[1] R-bloggers: 5 ways to measure running time of r code
[2] R-bloggers: timing in r
[3] CRAN: tictoc: Functions for timing R scripts, as well as implementations of Stack and List structures
[4] [책] R을 이용한 데이터 처리 & 분석 실무 - 명령문 실행 시간 측정'


banner-request-analysis

  1. 청크(chunk) : '하나의 덩어리'라는 뜻을 가지고 있으며, 실제 측정하고자 하는 '코드 묶음'을 나타냅니다. [본문으로]