public bigdata

[datacamp] 효율적인 R 코드 작성 본문

R programming

[datacamp] 효율적인 R 코드 작성

public bigdata 2020. 3. 14. 22:06

<benchmark>

 

1. R 버전 확인

> version
               _                           
platform       x86_64-w64-mingw32          
arch           x86_64                      
os             mingw32                     
system         x86_64, mingw32             
status                                     
major          3                           
minor          6.3                         
year           2020                        
month          02                          
day            29                          
svn rev        77875                       
language       R                           
version.string R version 3.6.3 (2020-02-29)
nickname       Holding the Windsock     
> version$major
[1] "3"
> version$minor
[1] "6.3"

version이라고 실행만 하면 된다.

 

2. read.csv VS read.RDS

> system.time(read.csv('movies.csv'))
   user  system elapsed 
  0.430   0.000   0.431
> 
> # How long does it take to read movies from RDS?
> system.time(readRDS('movies.rds'))
   user  system elapsed 
  0.045   0.000   0.045

3. system.time

  • user : time is the cpu time charged for the execution of user instructions.
  • system : time is the cpu time charged for execution by the system on behalf of the calling process
  • elapsed time is approximately the sum of user and system, this is the number we typically care about.
system.time(apply(iris[, -5], 1, sum))

4. library(microbehchmark)

> microbenchmark(apply(iris[, -5], 1, sum), times = 10)
Unit: microseconds
                      expr   min    lq   mean median    uq   max neval
 apply(iris[, -5], 1, sum) 231.8 232.8 249.92 237.65 242.2 354.8    10

times 인자를 통해 여러번 반복한 데이터로 부터 통계치를 산출해준다.

 

5. RAM, CPU 스펙 확인

library(benchmarkme)

ram <- get_ram()
ram
16.3 GB

# Assign the variable cpu to the cpu specs
cpu <- get_cpu()
cpu
$vendor_id
[1] "GenuineIntel"

$model_name
[1] "Intel(R) Xeon(R) Platinum 8175M CPU @ 2.50GHz"

$no_of_cores
[1] 4

 

6. 파일 읽고 쓰는데 걸리는 시간

# Load the package
library("benchmarkme")

# Run the io benchmark
res <- benchmark_io(runs = 1, size = 5)

# Plot the results
plot(res)
  • runs : n번씩 반복
  • size : 테스트할 데이터 사이즈(MB)

<Efficient Base R>

 

7. slow code example

> n <- 30000
> # Slow code
> growing <- function(n) {
+   x <- NULL
+   for(i in 1:n)
+     x <- c(x, rnorm(1))
+   x
+ }
> 
> res_grow <- system.time(growing(n = 30000))
> res_grow
 사용자  시스템 elapsed 
    1.2     0.0     1.2 

8. fast code example

> n <- 30000
> # Fast code
> pre_allocate <- function(n) {
+   x <- numeric(n) # Pre-allocate
+   for(i in 1:n) 
+     x[i] <- rnorm(1)
+   x
+ }
> n <- 30000
> system.time(res_allocate <- pre_allocate(n))
 사용자  시스템 elapsed 
   0.03    0.00    0.03

9. R은 loop를 여러번 실행하는 것보다. vector 연산이 훨씬 빠르다.

 

10. R에서는 데이터프레임에 대한 인덱싱보다 매트릭스에 대한 인덱싱이 훨씬 빠르다.

## 열선택
> data(iris)
> iris_df <- iris
> iris_mat <- as.matrix(iris)
> microbenchmark(iris_df[, 1], iris_mat[, 1])
Unit: microseconds
          expr min  lq  mean median  uq  max neval
  iris_df[, 1] 5.5 5.7 6.372    6.1 6.3 35.6   100
 iris_mat[, 1] 1.1 1.3 1.598    1.6 1.7  7.6   100


## 행선택
> data(iris)
> iris_df <- iris
> iris_mat <- as.matrix(iris)
> microbenchmark(iris_df[1, ], iris_mat[1, ])
Unit: nanoseconds
          expr   min    lq     mean median    uq    max neval
  iris_df[1, ] 41301 41951 45107.91  42401 42901 153402   100
 iris_mat[1, ]   600   651  1028.00   1101  1201   7902   100

그 중에서도 행 선택의 경우 훨씬 많이 차이가 난다. 컴퓨터의 데이터프레임과 매트릭스의 저장방식이 다르기 때문에 차이가 난다.


<Code Profiling>

 

11. 코드에서 병목현상이 생기는 곳 알아내기

# Load the data set
data(movies, package = "ggplot2movies") 

# Load the profvis pacprkage
library(profvis)

# Profile the following code with the profvis function
profvis({
  # Load and select data
  comedies <- movies[movies$Comedy == 1, ]
  
  # Plot data of interest
  plot(comedies$year, comedies$rating)
  # Loess regression line
  model <- loess(rating ~ year, data = comedies)
  j <- order(comedies$year)
  
  # Add fitted line to the plot
  lines(comedies$year[j], model$fitted[j], col = "red")
}) # Remember the closing brackets!

profvis라는 패키지를 이용해 위와 같이 profvis 함수 안에 실행시킬 코드를 {} 중괄호로 감싸 실행하면 어디서 시간이 오래 걸리는지 알 수 있다.

12. && vs &

Python의 and or 처럼 R의 &&, | 은  (False && ~~)인 경우 뒤 코드를 볼 필요가 없기 때문에 바로 결과를 내는 트릭을 사용한다. 그래서 &, | 보다는 속도가 훨씬 빠르다. 그러나 길이가 1인 비교만 가능하다.


<Parallel Programming>

 

13. R에서 병렬 컴퓨팅을 하는 프로세스

  1. Load the package

  2. Make a cluster

  3. Switch to parSapply

  4. stop cluster

## 기본 형태
> # Determine the number of available cores
> detectCores()
[1] 4
> 
> # Create a cluster via makeCluster
> cl <- makeCluster(spec = 2)
> 
> # Parallelize this code
> parApply(cl, dd, 2, median)
 [1]  6.312964e-02  5.311687e-02  1.681104e-02  1.433903e-02  7.064213e-05
 [6]  4.117414e-02  7.735636e-02 -5.979659e-02  2.297182e-01  1.100425e-01
> 
> # Stop the cluster
> stopCluster(cl)
## Full 형태 
play <- function() {
  total <- no_of_rolls <- 0
  while(total < 10) {
    total <- total + sample(1:6, 1)

    # If even. Reset to 0
    if(total %% 2 == 0) total <- 0 
    no_of_rolls <- no_of_rolls + 1
  }
  no_of_rolls
}


# Set the number of games to play
no_of_games <- 1e5

## Time serial version
system.time(serial <- sapply(1:no_of_games, function(i) play()))

## Set up cluster
cl <- makeCluster(spec = 4)
clusterExport(cl, "play")

## Time parallel version
system.time(par <- parSapply(cl, 1:no_of_games, function(i) play()))

## Stop cluster
stopCluster(cl)

위 코드에서 가장 중요한 부분 "## Set up cluster "에서 클러스터를 만들고 clusterExport 함수를 통해서 play를 지정해준다.


cl <- makeCluster(spec = 4)
clusterExport(cl, "play")

이렇게 하는 이유는 parSapply는 독립적인 worker(core)에서 작동이 된다. 각각의 worker는 library, object가 로딩되어 있지 않는 fresh한 R 세션이라고 한다. 그래서 병렬 처리를 하기 위해서는 필요한 library, object를 worker에 전달해줘야 한다. library는 clusterEvalQ함수를 사용해서 전달해주고, object, 함수는 clusterExport로 전달해준다. 

기본적으로 clusterExport 설정 시 envir 인자의 default는 .Grobal 환경이다. 그래서 따로 envir 를 설정할 필요 없이 필요한 함수만 전달해주면 된다.

그런데 만약 "parSapply(cl, 1:no_of_games, function(i) play())" 이 코드가 function내에서 실행이 되는 상황이고 함수에 전달된 인자를 찾아서 실행해야 하는 상황인데, envir인자를 default 상태로 실행하면 에러가 난다. 글로벌 환경에서 필요한 값을 찾기 때문이다. 여기서는 function에 전달된 인자들을 전달해야 하므로, 함수 내부의 환경을 참조할 수 있도록 envir = enviroment()라고 작성해줘야 제대로 작동이 된다. 자세한 내용은 더 찾아봐야 한다. R의 환경 관련해서.

 

14. apply family 

  • apply -> parApply
  • sapply -> parSapply
  • lapply -> parLapply