본문 바로가기

회사생활/R

R apply 계열 함수 총 정리 2 ( lapply / mclapply )

apply 계열 함수 총 정리 2 ( mclapply )




apply 계열 함수를 정리하는 시리즈 중 멀티코어를 이용하여 빠른 속도로 반복문을 처리하는 mclapply를 다뤄보고자 한다. 기본적인 apply 계열 함수에 대한 이해와 mcapply 외의 함수들에 대한 설명은 아래 포스팅을 참고하자.



▼ apply 계열 함수에 대한 기본적인 이해가 필요하다면? ▼

2017/06/25 - [Analysis/R] - R apply 계열 함수 총 정리 1 ( apply / lapply / sapply / vapply )




-

(1) R 멀티코어(Multi Core) 사용을 위한 환경

(2) apply 계열, 특히 mclapply는 얼마나 빠른가?

(3) 첫번째 테스트 : iris를 500번 rbind 하기

(4) 두번째 테스트 : iris*100 데이터를 행 단위로 paste 처리하기

(5) 처리속도 비교결과 확인

-




(1) R 멀티코어(Multi Core) 사용을 위한 환경


일단 멀티코어를 사용하려면 Windows 운영체제가 아닌 리눅스나 맥OS 운영체제 위에 깔려있는 R이 필요하다는 점을 알려준다. Windows에서도 멀티코어를 사용하는 함수들을 사용할 수는 있지만 멀티코어 효과가 발현되지는 않는다.


내가 예제를 진행한 환경은 Linux 환경이니 참고하자.




(2) apply 계열, 특히 mclapply는 얼마나 빠른가?


for문 대비 얼마나 빠른지 테스트 하기 위해 두 가지 테스트를 하려고 한다.

첫번째 테스트는 iris 데이터를 500번 rbind 한 형태의 데이터를 만드는 데 걸리는 시간을 측정해 비교하는 테스트이고 두번째 테스트는 iris 데이터를 100번 rbind 한 형태의 데이터 즉, 15000 x 5 데이터를 행별로 paste 처리하는 데 걸리는 시간을 측정해 비교하는 테스트이다. 자세한 내용은 아래 코드를 보면 이해하기 쉬울 것 같다.




(3) 첫번째 테스트 : iris를 500번 rbind 하기


먼저 테스트 환경을 만들기 위해 필요한 library를 불러오고 environment를 모두 정리하여 메모리를 여유있게 해준다.

library(plyr)       # ldply
library(parallel)   # mclapply

rm(list=ls())
gc()

테스트 결과인 처리시간을 저장할 result 매트릭스도 만들어준다.

result <- matrix(NA, ncol = 2, nrow = 3)
rownames(result) <- c("for", "lapply", "mclapply5")
colnames(result) <- c("rbind500", "paste")

테스트는 for, lapply, 5코어 mclapply 코드에 대한 처리 속도를 비교할 것이므로 3개 행으로 이루어진 result 매트릭스를 만들었고 각 컬럼은 테스트 1번과 테스트 2번을 의미한다.



첫번째 테스트 for문 처리시간 측정 코드

## Test1 : for
dtm1_0 <- proc.time()
for_result <- NULL
for(i in 1:500) {
  for_result <- rbind(for_result, iris)
}
dtm1_1 <- proc.time() - dtm1_0

## elapsed 시간을 본다.
print(dtm1_1)

rm(for_result)
gc()



첫번째 테스트 lapply문 처리시간 측정 코드

dtm2_0 <- proc.time()

l_result <- lapply(1:500, function(x) {
  return(iris)
})
l_result <- ldply(l_result)

dtm2_1 <- proc.time() - dtm2_0
rm(l_result)
gc()


첫번째 테스트 mclappy문 처리시간 측정 코드 (5 Core 사용)
dtm3_0 <- proc.time()

mcl_result <- mclapply(1:500, function(x) {
  return(iris)
}, mc.cores = 5)
mcl_result <- ldply(mcl_result)

dtm3_1 <- proc.time() - dtm3_0
rm(mcl_result)
gc()

결과는 두번째 테스트까지 수행 후 확인하겠다.




(4) 두번째 테스트 : iris*100 데이터를 행 단위로 paste 처리하기


-
-

먼저 이번 테스트를 위해 iris가 100번 rbind 된 df라는 데이터를 만들어준다.

df <- lapply(1:100, function(x) {
  return(iris)
})
df <- ldply(df)


이번 테스트는 iris의 4개 숫자 컬럼을 paste 함수를 이용해 하나의 컬럼으로 만들고 Species를 붙여 5개 컬럼인 iris 데이터를 2개 컬럼으로 만드는 것이다.



두번째 테스트 for문 처리시간 측정 코드

dtm1_0 <- proc.time()

for_result <- NULL
for(i in 1:nrow(df)) {
  numvar <- paste(df[i, 1:4], collapse = " ")
  species <- df[i, 5]
  tmp <- data.frame(numvar, species)
  for_result <- rbind(for_result, tmp)
}

dtm1_2 <- proc.time() - dtm1_0
rm(for_result)
gc()


두번째 테스트 lapply문 처리시간 측정 코드

dtm2_0 <- proc.time()

l_result <- lapply(split(df, seq(nrow(df))), function(x) {
  numvar <- paste(x[1:4], collapse = " ")
  species <- x[5]
  return(data.frame(numvar, species))
})
l_result <- ldply(l_result)

dtm2_2 <- proc.time() - dtm2_0
rm(l_result)
gc()


두번째 테스트 mclappy문 처리시간 측정 코드 (5 Core 사용)

dtm3_0 <- proc.time()

mcl_result <- mclapply(split(df, seq(nrow(df))), function(x) {
  numvar <- paste(x[1:4], collapse = " ")
  species <- x[5]
  return(data.frame(numvar, species))
}, mc.cores = 5)
mcl_result <- ldply(mcl_result)

dtm3_2 <- proc.time() - dtm3_0
rm(mcl_result)
gc()



이제 저장해둔 처리 시간들을 result 매트릭스에 입력한 후 result를 출력해본다.

result[1,1] <- dtm1_1[3]
result[2,1] <- dtm2_1[3]
result[3,1] <- dtm3_1[3]

result[1,2] <- dtm1_2[3]
result[2,2] <- dtm2_2[3]
result[3,2] <- dtm3_2[3]

result



(5) 처리속도 비교결과 확인


아래는 result 매트릭스의 출력 결과로 숫자의 단위는 초이다. 숫자가 작을수록 동일한 결과를 짧은 시간 내에 만들 수 있다는 의미이다.


          rbind500  paste
for          2.086 20.530
lapply       0.039  5.501
mclapply5    0.102  3.095



이렇게 두 가지 테스트를 통해 일단 for문이 apply 계열 함수들보다 현저하게 느리는 것을 확인할 수 있었다. 물론 어떤 연산이냐에 따라 멀티코어가 중요할 수도 아닐 수도 있으니 항상 테스트가 필요하다.


rbind 500번 반복과 같이 단순한 처리를 위한 반복문은 멀티코어를 이용하는 것이 오히려 오래 걸릴 수 있다. 하지만 행 단위 처리 등과 같이 오래 걸리는 연산이 있을 수록 apply 계열, 특히 mclapply가 빛을 바라는 것을 확인할 수 있다. 


처음 R을 접하는 사람일수록 apply 계열의 사용이 어색할 수 있지만 되도록이면 for문 대신 apply 계열 함수를 이용하도록 하자.