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 time
은elapsed time
을 구성하는 요소로, 각각 프로그램 코드 자체를 수행하는 데 걸린 시간과 프로그램이 운영체제의 명령을 호출했다면 그 때 운영체제가 명령을 수행하는 데 걸린 시간을 의미한다.
- 출처 : [책] R을 이용한 데이터 처리 & 분석 실무 - 명령문 실행 시간 측정
4. Library rbenchmark
R 패키지 rbenchmark
를 설명한 문서에서는 benchmark
함수를 'system.time() 을 간단하게 포장한 것'이라고 설명하고 있습니다. 그러나 system.time
에 비해 많은 편의성을 제공합니다. 예를 들어, benchmark
다수의 식에 여러번의 복제를 지정하는데에 단 한 번의 호출만 필요합니다. 또한 반환된 결과는 데이터 프레임(data.frame)으로 편리하게 구성됩니다.
아래는 3가지 방법을 사용하여 선형 회귀 계수를 계산하여 비교하는 예제 입니다.
- lm
- the Moore-Penrose pseudoinverse
- 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.self
는 system.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)
결론
위에서 소개드린 'R 실행 시간(running time) 측정 방법'은 사용성이 높은 예제 위주로 설명드렸기에 완벽하다고 볼 수는 없습니다. 그러나 개인적인 결론을 내려본다면 아래와 같습니다.
tictoc
패키지 뿐만 아니라Sys.time
함수는 복잡한 알고리즘(중첩된)의 실행 시간을 측정할 때 사용할 수 있습니다. 그러나 개인적으로는tictoc
패키지를 선호합니다.microbenchmark
가 실행 시간 측정 이외에 다른 유형의 측정 값을 반환하는 것을 확인할 수 있었으며, 개인적으로 대부분의 상황에서의microbenchmark
측정은 보다 실질적인 의미가 있다고 생각합니다. 개인적인 지식 범위에서는microbenchmark
가 시각화 기능이 내장된 유일한 벤치마킹 패키지로 활용도 측면에 우수성이 있다고 생각됩니다.
위와 같은 이유들로, 저는 앞으로 microbenchmark
와 tictoc
을 사용할 예정입니다. :)
관련 링크
[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을 이용한 데이터 처리 & 분석 실무 - 명령문 실행 시간 측정'
- 청크(chunk) : '하나의 덩어리'라는 뜻을 가지고 있으며, 실제 측정하고자 하는 '코드 묶음'을 나타냅니다. [본문으로]