1 병렬 컴퓨팅 1 2

병렬 컴퓨팅(parallel computing)의 기본 개념은 한번에 하나씩 처리하게 되면 상당시간 걸릴 작업을 작업을 동시에 실행킬 수 있도록 작업을 나누어서 짧은 시간에 전체 작업을 완료하는 것이다.

방의 4면에 도배를 하거나 페인트를 칠하는 것을 예로 들어보자. 도배나 페이트 작업의 경우 문제를 서로 다른 작업 4개를 나눌 수 있다; 즉, 방의 각 면을 도배나 페이트 칠하는 작업. 원칙적으로 보면, 각 작업은 독립적이라 한면을 도배한 다음에 다른 면을 도배할 필요는 없다. 그렇다고 각 도배 작업을 한번에 혹은 병렬로 실행될 수 있다는 것을 의미하지도 않는다. 각 도배 작업을 수행하는데 딸린 자원에 따라 달라질 수도 있다.

순차 vs 병렬

1.1 동시 실행과 병렬 실행

만약 도배를 하는 도배공이 한명이면, 한동안 남쪽벽에 벽지를 먼저 바르고 나서, 서쪽벽에 벽지 작업을 한동안 수행하고, 동쪽벽, 북쪽벽 이런 순으로 도배작업을 수행한다. 작업은 동시 실행(concurrent execution)되지만 병렬 실행(parallel execution)은 아니다. 왜냐하면 한번에 한 도배 작업만 수행되기 때문이다. 만약 도배공이 2명 이상이라면 작업을 병렬로 처리할 수 있다.

HPC 측면에서 본다면, 도배공은 컴퓨터 CPU 코어 갯수를 나타낸다. 이용가능한 CPU 코어 갯수가 병렬로 수행가능한 최대 작업 갯수를 결정한다. 하지만, 동시에 시작될 수 있는 동시 작업(concurrent tasks) 갯수는 한정되지 않는다.

멀티코어 vs 분산 컴퓨터

1.2 동기실행과 비동기 실행

이번에는 모든 도배 대신에 페이트로 방의 4면 천장을 포함하면 5면을 색칠하는 상황을 가정하자. 페인트는 방 중앙에 위한 대형 페이트통에서 받아와서 각 벽면과 천장을 색칠한다. 색칠 작업에 동원된 모든 페인트공이 다른 색상을 사용한다면 비동기(asynchronous) 방식으로 색칠 작업을 수행할 수 있다. 하지만, 벽면과 천장의 색상을 동일한 것으로 맞춰 작업하는데, 작업중인 페인트가 동시에 없어진다면, 중앙에 위치한 페인트통으로부터 각자 작업중인 작업 페인트통을 채우려면 동기화(synchronous)해야 한다; 즉, 다른 페이트공이 페인트를 받아갈 때까지 줄을 서서 대기해야 한다.

HPC 측면에서 본다면, 중앙에 위치한 페인트통은 컴퓨터의 메인메모리 접근에 해당된다. 프로그램이 작성된 방식에 따라, 메모리의 데이터 접근이 동기방식일수도 , 비동기 방식일 수도 있다.

1.3 분산 메모리과 공유 메모리

마지막으로, 각 페인트공 각자에 페인트통이 각자 있는 경우를 가정해보자. 이 시나리오 아래에서, 각 페인트공(worker)은 완전히 본인 힘으로 페인트 작업을 완료할 수 있다. 페인트공이 심지어 같은 방에서 작업할 필요도 없다. 각 작업자는 집의 다른 방을 페인트 칠할 수도 있고, 도시의 다른 주택을 색칠할 수도 있고, 한 국가의 다른 도시에서 페인트 작업을 수행할 수도 있다. 하지만, 대다수의 경우 적절한 커뮤니케이션이 필요하다. 작업자 A씨가 작업자 B씨만 쓸 수 이쓴ㄴ 페이트통에 담긴 색상이 필요하다고 생각해 보자; 작업자 A씨는 페이트가 필요하다는 요청을 작업자 B씨에게 보내고, 작업자 B씨는 요청한 색상의 페인트를 보내서 대응을 해야 한다.

HPC 측면에서 본다면, 작업자의 서로 다른 페이트통은 클러스터 컴퓨터/노드에 분산된 메모리로 볼 수 있다. 조밀한(fine-grained) 병렬 프로그램은 작업간에 상당히 많은 커뮤니케이션과 동기화가 필요한 바면, 성긴(coarse-grained) 병렬 프로그램은 거의 커뮤니케이션이 필요하지 않는다. 쑥스럽게/대량(embarrassingly/massively) 병렬 프로그램은 완전히 모든 작업을 독립적으로 실행된다(커뮤니케이션이 필요없음).

공유 메모리

1.4 프로세스 vs. 쓰레드

페이터 작업자는 양손잡이로 두손을 가지고 있어 동시에 두 팔로 작업을 수행할 수 있다. 기술적으로 한 팔로 작업하는 작업은 페이터 작업자 한명이 작업하는 것에 대응된다.

HPC 측면에서 본다면, 각 페이터 작업자는 프로세서(프로그램 개별 인스턴스 한개)에 대응된다. 페이터 작업자의 한 팔은 프로그램 “쓰레드(Thread)”를 나타낸다. 쓰레드는 단일 프로그램 내에 실행 분기점이 되고, 동식 혹은 비동기 방식으로 실행될 수 있다.


2 실무에 병렬처리 적용 방법

2.1 비동기 프로그래밍

일부 계산작업을 실행할 때 대기시간이 긴 경우가 더러 있다. 아마도 인터넷 웹서버에 정보를 보내고 응답을 기다리는 경우가 이에 해당된다. 인터넷으로 대량의 요청사항을 보낼 경우, 작성한 프로그램이 단지 응답만 받는데도 한 세월이 걸릴 수 있다. 이런 사나리오 아래에서는 인터넷에 응답을 마구 보내고 나서, 개별적으로 요청사항을 처리하기 전에 각각에 대해서 대기를 타기 보다는 주기적으로 요청사항이 완료되었는지를 확인하는 것이 여러모로 이롭다.

이 경우가 전형적인 비동기 프로그래밍에 해당된다. 쓰레드 하나가 동시에 많은 작업을 수행하고, 주기적으로 작업 진행경과를 점검하고, 외부 작업이 완료되었을 때만 적절한 조치를 취한다. 대기가 빈번하게 발생하는 웹 프로그래밍을 할 때 비동기 프로그래밍이 매우 중요하다. 파이썬에서는 이런 목적으로 asyncio 모듈을 사용하고, R에서는 future 팩키지를 사용한다. 과학 프로그램에서는 그다지 유용하지는 않다. 이유는 코어/쓰레드 하나가 일반적으로 임의 작업을 수행하기 때문이다 - 병렬로 실행되지 않는 보통 프로그램이 빠를 수 있다.

2.2 공유 메모리 프로그래밍

공유 메모리 프로그래밍(shared memory programming)은 컴퓨터 한대의 자원을 이용하여, 메모리 위에 데이터셋 하나를 올려놓고 다수 쓰레드 혹은 프로세스가 함께 작업을 수행한다. 이 방식이 병렬 프로그래밍의 가장 흔한 형태로 상대적으로 쉽다. 파이썬에서는 multiprocess / multiprocessing 모듈을 사용한다. R에서는 foreach 등 다양한 팩키지가 있지만, multicore, doMC 팩키지를 caret에서 많이 사용된다.

2.3 분산 메모리 프로그래밍

공유 메모리 프로그래밍은 매우 유용하지만, 한가지 중요한 한계점이 존재한다; 단일 컴퓨터에 설치된 CPU 코어 갯수만 사용할 수 있다는 점이다. 좀더 프로그램 실행 속도를 높이고자 한다면, 더 고성능 컴퓨터가 필요하다. 고성능 컴퓨터로 갈수록 가격이 훨씬 훨씬 더 올라간다. 이런 경우 저사양 가성비가 뛰어난 컴퓨터를 대신에 사용하는 것이 더 효율적이지 않을까?

분산 메모리 컴퓨팅(distributed memory programming) 뒤에는 이러한 정당성이 담보되어 있다. 작업을 다수 컴퓨터에 분산시켜 실행시키는데, 컴퓨터 각각은 문제의 일부분만 담당해서 처리한다. 각 컴퓨터가 수행한 작업을 커뮤니케이션을 통해 컴퓨터 노드간에 공유한다.

컴퓨터 메모리에 담기에는 너무 큰 데이터를 작업할 경우 가장 장점이 많다. MPI, Hadoop, Spark가 빅데이터를 처리하는데 가장 널리 사용되는 프레임워크다.

openMP, MPI, CUDA

2.4 연쇄 작업(serial farming)

동일한 연산작업을 다수 반복하는 경우가 많다. 아마도, 서로 다른 표본 10개에 대해서 같은 작업을 실행하는 경우가 이에 해당된다. 이런 작업은 어떤 커뮤니케이션도 필요하지는 않고, 각 작업은 완전히 서로 독립적이다.

이런 시나리오에서는, 진짜 멋진 병렬 프로그래밍 기법을 왜 적용하는 대신에 10대 컴퓨터를 동원해서 서로 다른 데이터셋 10개에, 동일한 프로그램을 10번 실행하기만 하면 된다. 작업은 병렬로 진행되고 목적을 달성하는데 프로그램을 바꿀 필요는 하나도 없다. 추가로 좋은 점은, 어떤 작업을 하든, 프로그램 언어에 상관없이 모든 프로그램에 동일하게 동작한다는 점이다.

이러한 기법을 연쇄 작업(serial farming)이라고 부르는데 snakemake를 사용해서 독립적인 수백 혹은 수천가지 작업은 아니지만, 수십가지 병렬 작업을 무난히 처리할 수 있다.

snakemake