xwMOOC 데이터 과학
멀티코어 병렬 프로그래밍 - doParallel
1. 회문 (Palindrome) 1
문자에 대한 회문은 도처에서 많이 찾을 수 있다. 숫자에 대한 회문을 바탕으로 멀티코어를 활용하여 병렬프로그램을 작성하는 방법을 살펴본다.
get_digit
함수를 통해 입력받은 숫자의 n번째 자리수 숫자를 뽑아내는 함수를 작성하고 이를 바탕으로 회문 숫자인지를 palindrome
함수로 판별한다. 그리고 숫자 자리수에 맞춰 첫째자리, 둘째자리, … 순으로 두 숫자를 곱해서 나온 숫자 중 가장 큰 회문 숫자를 biggest_palindrome
함수를 통해 찾아보자.
# 0. 환경설정 --------
# library(doParallel)
# library(tidyverse)
# 1. 기본함수 -------
## 1.1. 숫자의 n번째 자리수 숫자를 뽑아내는 함수
function(num, n) {
get_digit <-# 왼쪽부터 숫자의 자리수를 제거하고 나서, 오른쪽 자리수 숫자를 제거
%% (10 ^ n)) %/% (10 ^ (n-1))
(num
}
## 1.2. 양의 정수가 회문인지 판정하는 함수
function(num) {
palindrome <- floor(log(num, 10)) + 1
digits <-for (x in 1:((digits %/% 2))) {
get_digit(num, x)
digit1 <- get_digit(num, (digits + 1) - x)
digit2 <-if (digit1 != digit2)
return(FALSE)
}return(TRUE)
}
## 1.3. 세자리, 네자리, 다섯자리 ... 등등 가장 큰 회문 숫자를 탐색
function(digits=3) {
biggest_palindrome <-
10^(digits-1)
min_digit <- 10^digits -1
max_digit <-
0
best <-
for (x in min_digit:max_digit) {
for (y in x:max_digit) {
x * y
candidate <-if (candidate > best && palindrome(candidate)) {
candidate
best <-
}
}
}return(best)
}
상기 함수를 통해 순차적으로 계산된 결과는 다음과 같다.
- 십자리:
biggest_palindrome(1)
를 실행하면 9. - 백자리:
biggest_palindrome(2)
를 실행하면 9009. - 천자리:
biggest_palindrome(3)
를 실행하면 906609. - 만자리:
biggest_palindrome(4)
를 실행하면 99000099.
2. 병렬처리 2
순차 컴퓨팅은 문제를 일련의 이산 명령어 집합으로 나눠서 순차적으로 단일 CPU 프로세서로 처리하는 반면에, 병렬 컴퓨팅은 문제를 몇개에서 수십개의 CPU 프로세서(코어)로 나누어서 처리 가능한 형태로 변형한 후에 병렬적으로 처리한다.
플린 분류(영어: Flynn taxonomy)는 마이클 플린이 1966년에 제안한 컴퓨터 아키텍처 분류로, 일반인이 쉽게 이해하는 방식으로 보면 처리할 데이터와 처리할 CPU 프로세서에 따라 다음과 같이 네가지로 구분한다.
- SISD(Single Instruction, Single Data stream)
- SIMD(Single Instruction, Multiple Data streams)
- MISD(Multiple Instruction, Single Data stream)
- MIMD (Multiple Instruction, Multiple Data streams)
2.1. for
구문
가장 먼저 for
문을 통해 총 소요시간을 구해본다.
# 2. 순차처리부터 병렬처리 -------
## 2.1. 순차처리 -----
numeric(4)
elapsed_for_v <-
for(i in 1:4) {
system.time(biggest_palindrome(i))[3]
elapsed_for_v[i] <-
}
elapsed_for_v
[1] 0.00 0.02 0.16 4.42
2.2. for
구문
foreach
를 통해 순차적인 병렬처리를 수행해본다.
## 2.2. 순차처리 foreach -----
numeric(4)
elapsed_foreach_v <-
foreach(i = 1:4) %do% {
system.time(biggest_palindrome(i))[3]
elapsed_foreach_v[i] <- }
[[1]]
elapsed
0
[[2]]
elapsed
0
[[3]]
elapsed
0.17
[[4]]
elapsed
4.58
elapsed_foreach_v
[1] 0.00 0.00 0.17 4.58
2.3. foreach
병렬 구문
최근 컴퓨터를 구입하게 되면 대부분 멀티코어가 들어있는 하드웨어를 갖고 있다. 따라서, 병렬처리를 위해서는 makeCluster
를 통해 코어로 구성된 클러스터를 구성하고 나서 이를 병렬처리를 수행한다.
## 2.3. 병렬처리 foreach -----
makeCluster(detectCores())
cl <-registerDoParallel(cl)
elapsed_parallel_v <- system.time (
foreach(i=1:4, .combine='rbind.data.frame',
palindrome_df <-.multicombine=TRUE,
.export=c("get_digit","palindrome")) %dopar% {
biggest_palindrome(i)
}3]
) [
stopCluster(cl)
elapsed_parallel_v
elapsed
33
2.4. 결과 비교
자리수가 4자리가 되면 제법 소요시간이 소요된다. 5자리가 되면 유의민한 숫자가 될 것이지만 너무 큰 숫자라 더이상 회문 숫자를 구할 수가 없다. 그리고, 단일 코어에서 계산되면 빠를 것을 코어 다수에서 나눠 계산을 하게 되어 전체적인 시간이 더 늘어났다.
병렬처리작업을 수행할 경우 문제를 병렬처리가 가능한 형태로 나눠 각 코어에 분배하여 처리할 경우 원하는 효과를 거둘 수 있다.
# 3. 분석결과 정리 -----
data.frame("for루프" = elapsed_for_v, "foreach"=elapsed_foreach_v) %>%
mutate(병렬=elapsed_parallel_v,
c(10,100,1000,10000)) %>%
자리수= select(자리수, everything()) %>%
DT::datatable()
Error: <text>:5:10: unexpected '<'
4: data.frame("for<U+B8E8><U+D504>" = elapsed_for_v, "foreach"=elapsed_foreach_v) %>%
5: mutate(<
^