1 정보수집 프로파일링

정보수집 프로파일링(profiling)에 대해 이해하고, 프로파일링을 적용해서 성능병목점을 식별하고 성능을 향상시킨다. R코드 어느 부분에서 가장 많은 수행시간과 메모리 저장공간을 사용했는지 파악하는 것이 중요하다. 수행시간과 메모리 저장공간을 측정하는데 사용되는 도구가 정보수집 프로파일링(Profiling) 이다.

  • system.time : 간단히 수행시간을 측정하는 함수
  • Rprof : R코드 프로파일링하는 함수1
  • Rprofmem R 메모리 사용량을 프로파일링하는 함수

Rprof, Rprofmem 함수를 추상화해서 시각화하는 팩키지가 profviz 로 출시되었고, rbenchmark 는 비교하는 목적으로 사용되었고, 최근에는 bench 팩키지가 새롭게 도입되었다.

2 Rprof 프로파일링2

Rprof 함수는 정해진 시간, 예를 들어 interval = 0.02 기본디폴트 설정값으로 되어 20 \(frac{1}{1000}\) 천분의 1초로 측정을 한다. 측정결과는 지정한 경로명에 위치한 파일에 저장되고, summaryRprof 함수로 꺼내본다.

Rprof 동작 방식

Rprof 함수는 R에 기본 내장된 프로파일링 함수로 사용법은 다음과 같다.

Rprof("프로파일링 경과를 저장할 파일경로 및 파일명")
## 프로파일링을 통해 정보를 추적할 R 코드
Rprof(NULL)
## 프로파일링하지 않아 정보를 추정하지 않을 R코드 
Rprof("프로파일링 경과를 저장할 파일경로 및 파일명", append=TRUE)
## 프로파일링을 통해 정보를 추적할 R 코드
Rprof(NULL)
 
# 프로파일링 추적결과를 요약하고 정리
summaryRprof("프로파일링 경과를 저장할 파일경로 및 파일명")
$by.self
                      self.time self.pct total.time total.pct
sample.interval=20000      0.02      100       0.02       100

$by.total
                      total.time total.pct self.time self.pct
sample.interval=20000       0.02       100      0.02      100

$sample.interval
[1] 0.02

$sampling.time
[1] 0.02

2.1 Rprof 프로파일링 요약

  • by.self : 해당 함수가 온전히 사용한 시간
  • by.total : 해당 함수와 호출된 함수 모두에서 사용된 시간
  • sample.interval : 표본측정 시간, 기본디폴트설정으로 20 천분의 1초로 설정
  • sampling.time : 프로파일링을 통해 수행된 전체 시간.

추가로 한번만 측정하면 측정 중간에 쓰레기수거(Garbage Collection, GC) 작업 등으로 인해 정확한 실행시간 측정에 오차가 발생할 수 있다. 이를 방지하고자, replicate를 사용해서 반복측정한다.

2.2 Rprof 사례

example(glm)을 통해 일반화선형모형을 적합하여 모형을 개발하는데 사용된 시간을 살펴본다. 전체적으로 80 천분의 1초가 소요되었으며 “ifelse”, “psub”, “set”, “unlist”에서 시간이 균등하게 사용된 것을 볼 수 있다.

##========================================================================
## Rprof 예제
##========================================================================
# 1. 프로파일링 시작
Rprof(tmp <- tempfile())
example(glm)

glm> ## Dobson (1990) Page 93: Randomized Controlled Trial :
glm> counts <- c(18,17,15,20,10,20,25,13,12)

glm> outcome <- gl(3,1,9)

glm> treatment <- gl(3,3)

glm> data.frame(treatment, outcome, counts) # showing data
  treatment outcome counts
1         1       1     18
2         1       2     17
3         1       3     15
4         2       1     20
5         2       2     10
6         2       3     20
7         3       1     25
8         3       2     13
9         3       3     12

glm> glm.D93 <- glm(counts ~ outcome + treatment, family = poisson())

glm> anova(glm.D93)
Analysis of Deviance Table

Model: poisson, link: log

Response: counts

Terms added sequentially (first to last)

          Df Deviance Resid. Df Resid. Dev
NULL                          8    10.5814
outcome    2   5.4523         6     5.1291
treatment  2   0.0000         4     5.1291

glm> ## No test: 
glm> ##D summary(glm.D93)
glm> ## End(No test)
glm> ## Computing AIC [in many ways]:
glm> (A0 <- AIC(glm.D93))
[1] 56.76132

glm> (ll <- logLik(glm.D93))
'log Lik.' -23.38066 (df=5)

glm> A1 <- -2*c(ll) + 2*attr(ll, "df")

glm> A2 <- glm.D93$family$aic(counts, mu=fitted(glm.D93), wt=1) +
glm+         2 * length(coef(glm.D93))

glm> stopifnot(exprs = {
glm+   all.equal(A0, A1)
glm+   all.equal(A1, A2)
glm+   all.equal(A1, glm.D93$aic)
glm+ })

glm> ## No test: 
glm> ##D ## an example with offsets from Venables & Ripley (2002, p.189)
glm> ##D utils::data(anorexia, package = "MASS")
glm> ##D 
glm> ##D anorex.1 <- glm(Postwt ~ Prewt + Treat + offset(Prewt),
glm> ##D                 family = gaussian, data = anorexia)
glm> ##D summary(anorex.1)
glm> ## End(No test)
glm> 
glm> # A Gamma example, from McCullagh & Nelder (1989, pp. 300-2)
glm> clotting <- data.frame(
glm+     u = c(5,10,15,20,30,40,60,80,100),
glm+     lot1 = c(118,58,42,35,27,25,21,19,18),
glm+     lot2 = c(69,35,26,21,18,16,13,12,12))

glm> summary(glm(lot1 ~ log(u), data = clotting, family = Gamma))

Call:
glm(formula = lot1 ~ log(u), family = Gamma, data = clotting)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-0.04008  -0.03756  -0.02637   0.02905   0.08641  

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept) -0.0165544  0.0009275  -17.85 4.28e-07 ***
log(u)       0.0153431  0.0004150   36.98 2.75e-09 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for Gamma family taken to be 0.002446059)

    Null deviance: 3.51283  on 8  degrees of freedom
Residual deviance: 0.01673  on 7  degrees of freedom
AIC: 37.99

Number of Fisher Scoring iterations: 3


glm> summary(glm(lot2 ~ log(u), data = clotting, family = Gamma))

Call:
glm(formula = lot2 ~ log(u), family = Gamma, data = clotting)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-0.05574  -0.02925   0.01030   0.01714   0.06371  

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept) -0.0239085  0.0013265  -18.02 4.00e-07 ***
log(u)       0.0235992  0.0005768   40.91 1.36e-09 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for Gamma family taken to be 0.001813354)

    Null deviance: 3.118557  on 8  degrees of freedom
Residual deviance: 0.012672  on 7  degrees of freedom
AIC: 27.032

Number of Fisher Scoring iterations: 3


glm> ## Aliased ("S"ingular) -> 1 NA coefficient
glm> (fS <- glm(lot2 ~ log(u) + log(u^2), data = clotting, family = Gamma))

Call:  glm(formula = lot2 ~ log(u) + log(u^2), family = Gamma, data = clotting)

Coefficients:
(Intercept)       log(u)     log(u^2)  
   -0.02391      0.02360           NA  

Degrees of Freedom: 8 Total (i.e. Null);  7 Residual
Null Deviance:      3.119 
Residual Deviance: 0.01267  AIC: 27.03

glm> tools::assertError(update(fS, singular.ok=FALSE), verbose=interactive())

glm> ## -> .. "singular fit encountered"
glm> 
glm> ## Not run: 
glm> ##D ## for an example of the use of a terms object as a formula
glm> ##D demo(glm.vr)
glm> ## End(Not run)
glm> 
glm> 
Rprof()

# 2. 프로파일링 결과 요약
summaryRprof(tmp)
$by.self
        self.time self.pct total.time total.pct
"eval"       0.04    66.67       0.06    100.00
"remap"      0.02    33.33       0.02     33.33

$by.total
                      total.time total.pct self.time self.pct
"eval"                      0.06    100.00      0.04    66.67
"block_exec"                0.06    100.00      0.00     0.00
"call_block"                0.06    100.00      0.00     0.00
"evaluate"                  0.06    100.00      0.00     0.00
"evaluate::evaluate"        0.06    100.00      0.00     0.00
"evaluate_call"             0.06    100.00      0.00     0.00
"handle"                    0.06    100.00      0.00     0.00
"in_dir"                    0.06    100.00      0.00     0.00
"knitr::knit"               0.06    100.00      0.00     0.00
"process_file"              0.06    100.00      0.00     0.00
"process_group"             0.06    100.00      0.00     0.00
"process_group.block"       0.06    100.00      0.00     0.00
"rmarkdown::render"         0.06    100.00      0.00     0.00
"timing_fn"                 0.06    100.00      0.00     0.00
"withCallingHandlers"       0.06    100.00      0.00     0.00
"withVisible"               0.06    100.00      0.00     0.00
"example"                   0.04     66.67      0.00     0.00
"remap"                     0.02     33.33      0.02    33.33
"glm"                       0.02     33.33      0.00     0.00
"of0"                       0.02     33.33      0.00     0.00
"of1"                       0.02     33.33      0.00     0.00
"paste0"                    0.02     33.33      0.00     0.00
"render"                    0.02     33.33      0.00     0.00
"renderDont"                0.02     33.33      0.00     0.00
"source"                    0.02     33.33      0.00     0.00
"summary"                   0.02     33.33      0.00     0.00
"tools::Rd2ex"              0.02     33.33      0.00     0.00
"writeLines"                0.02     33.33      0.00     0.00
"WriteLines"                0.02     33.33      0.00     0.00

$sample.interval
[1] 0.02

$sampling.time
[1] 0.06

2.3 Rprof 시각화

숫자를 통해 프로파일링 결과를 살펴보는 것도 의미가 있지만, 시각화를 통해 전체적인 프로파일링 정보를 살펴본다. profr 팩키지를 설치하고 ggplot2 시각화 팩키지로 프로파일링된 결과를 살펴본다.

일반선형모형을 적합시킨 결과 콜스택 깊이가 20까지 올라간 것도 확인이 된다.

##========================================================================
## Rprof 시각화
##========================================================================

# install.packages("profr")
library(profr)
library(ggplot2)
x  <- profr(example(glm))
ggplot(x)

2.4 diamonds 회귀분석 프로파일링 예제

profr로 시각화하는데 data("diamonds") 부터 abline(lm.m, col = "blue")까지 R코드 실행부분을 별도 R스크립트로 저장하였다.

##========================================================================
## 다이아몬드 회귀분석
##========================================================================
# 0. 팩키지 가져오기
library(tidyverse)
# 1. 텍스트 정보
Rprof(tmp <- tempfile())
data("diamonds")
# 1. 산점도
plot(price ~ carat, data=diamonds)
# 2. 선형모형 적합
lm.m <- lm(price ~ carat, data=diamonds)
# 3. 데이터와 모형 적합 평가
abline(lm.m, col = "blue")

Rprof()

summaryRprof(tmp)
$by.self
               self.time self.pct total.time total.pct
"plot.default"      0.80    83.33       0.94     97.92
"paste"             0.08     8.33       0.08      8.33
"deparse1"          0.02     2.08       0.10     10.42
"Axis.default"      0.02     2.08       0.02      2.08
"get"               0.02     2.08       0.02      2.08
"plot.new"          0.02     2.08       0.02      2.08

$by.total
                      total.time total.pct self.time self.pct
"block_exec"                0.96    100.00      0.00     0.00
"call_block"                0.96    100.00      0.00     0.00
"eval"                      0.96    100.00      0.00     0.00
"evaluate"                  0.96    100.00      0.00     0.00
"evaluate::evaluate"        0.96    100.00      0.00     0.00
"evaluate_call"             0.96    100.00      0.00     0.00
"handle"                    0.96    100.00      0.00     0.00
"in_dir"                    0.96    100.00      0.00     0.00
"knitr::knit"               0.96    100.00      0.00     0.00
"plot"                      0.96    100.00      0.00     0.00
"process_file"              0.96    100.00      0.00     0.00
"process_group"             0.96    100.00      0.00     0.00
"process_group.block"       0.96    100.00      0.00     0.00
"rmarkdown::render"         0.96    100.00      0.00     0.00
"timing_fn"                 0.96    100.00      0.00     0.00
"withCallingHandlers"       0.96    100.00      0.00     0.00
"withVisible"               0.96    100.00      0.00     0.00
"plot.default"              0.94     97.92      0.80    83.33
"do.call"                   0.94     97.92      0.00     0.00
"plot.formula"              0.94     97.92      0.00     0.00
"deparse1"                  0.10     10.42      0.02     2.08
"paste"                     0.08      8.33      0.08     8.33
"Axis.default"              0.02      2.08      0.02     2.08
"get"                       0.02      2.08      0.02     2.08
"plot.new"                  0.02      2.08      0.02     2.08
"Axis"                      0.02      2.08      0.00     0.00
"localAxis"                 0.02      2.08      0.00     0.00

$sample.interval
[1] 0.02

$sampling.time
[1] 0.96

상기 코드를 스크립트 파일(diamonds-prof-ex.R)로 저장시킨 후에 이를 실행시킨다.

# 2. 시각화
library(profr)
library(ggplot2)
x <- profr(source("code/diamonds-prof-ex.R"))

ggplot(x)

3 profvis 팩키지

profvis 팩키지 profvis() 함수를 사용하게 되면 R마크다운 문서에도 관련 내용을 담을 수가 있고 앞선 내용을 최신 방식으로 RStudio IDE 에서도 실행이 가능하다.

profvis::profvis({
    ##========================================================================
    ## 다이아몬드 회귀분석
    ##========================================================================
    # 0. 팩키지 가져오기
    library(tidyverse)
    # 1. 텍스트 정보
    data("diamonds")
    # 1. 산점도
    plot(price ~ carat, data=diamonds)
    # 2. 선형모형 적합
    lm.m <- lm(price ~ carat, data=diamonds)
    # 3. 데이터와 모형 적합 평가
    abline(lm.m, col = "blue")
}, height = "250px")

 

데이터 과학자 이광춘 저작

kwangchun.lee.7@gmail.com