Computer

병렬 컴퓨팅, 멀티 프로세싱, python GIL

Novelism 2023. 3. 5. 00:00

 

 2007년은 제가 대학원에 입학한 해이고, 본격적으로 컴퓨터를 활용한 계산 연구를 시작한 해입니다.

 그리고 멀티코어 CPU들이 보급되기 시작한 시기이기도 합니다.

 뭐 서버는 그전부터 2개의 CPU를 장착하거나, 여러 서버들을 연결하여 클러스터를 구성하고 있었습니다만...

이때쯤 본격적으로 개인용 컴퓨터(PC) 에도 여러 개의 코어가 장착되어 병렬 컴퓨팅을 활용할 수 있게 되었습니다.

 아무튼 제가 연구를 시작할 시기부터 병렬 컴퓨팅은 (컴퓨터 자원을 어느 정도 필요로 한다면) 당연히 활용해야 하는 기술이었습니다.

 멀티 코어 CPU 외에도 한 코어를 논리적으로 2개의 코어처럼 취급하려는 하이퍼스레딩 (인텔) 이 나오긴 했지만, 실제 성능 향상이 거의 없어서 활용하지 못하다가 최근에는 30% 정도의 성능 향상이 있어서 잘 활용되고 있습니다. AMD도 동시 멀티스레딩 (Simultaneous Multi-Threading, SMT)을 지원하고 있습니다.

 

병렬 컴퓨팅, 분산 컴퓨팅, 멀티 프로세싱 등은 다들 유사한 의미를 가지지만, 병렬 컴퓨팅은 환경이나 활용가능한 방법이 넓다 보니 지칭하는 의미도 조금씩 다릅니다.

 컴퓨터가 여러 대 있는 경우, 한대에 여러 개의 프로세서가 있는 경우, 메모리를 공유하는 경우, 공유하지 않는 경우... 등등

분산 컴퓨팅은 보통은 컴퓨터가 여러 개일 경우를 지칭합니다.

 

병렬 컴퓨팅을 이야기하려면 프로세서와 프로세스의 관계에 대해 이야기를 해야 합니다.

 프로세서는 연산장치를 의미하고 프로세스는 소프트웨어를 의미합니다.

 과거에는 CPU, 지금은 코어, 혹은 스레드가 프로세서에 해당됩니다.

1 cpu  8 코어 16 스레드 라면, 16개의 프로세서가 있다고 생각하면 됩니다.

프로세스라는 것은 처리 단위라고 생각하면 되고 프로세스는 보통 실행파일을 하나 실행하면 프로그램이 하나 실행되는데,

 이런것들이 프로세스입니다. 물론 프로세스를 동시에 여러 개를 실행하거나, 하나의 (부모) 프로세스가 자식 프로세스를 여러 개 생성하게 하는 것도 가능합니다.

 

병렬 컴퓨팅이라고 거창하게 말했지만, 원리는 단순한 경우도 있습니다.

 예를들어 동일한 시뮬레이션을 파라미터만 바꿔서 100개를 실행해서 결과를 보고 싶은 경우가 있다면 어떨까요?

 그리고 컴퓨터가 한 10대 정도 있다고 가정합시다.

 서로 다른 시뮬레이션들은 서로 독립적으로 진행 가능합니다. 그냥 100개의 일을 병렬로 진행해야 하는 상황입니다.

 그러면 100개의 작업을 10개로 나누고, 각 컴퓨터마다 10개씩 실행하면 되겠죠. 컴퓨터에 프로세스와 메모리가가 많다면 10개를 한 번에, 아니면 순차적으로 하나가 끝나면 다른 하나가 시작되도록 실행할 것입니다.

 

 조금 화가 나는 기억이 떠오르는데... 당시 제가 연구실 처음 들어가서 코어 2 쿼드.. 4개의 멀티코어 CPU가 처음 나오는 상황에 연구실 컴퓨터를 바꾸기로 했습니다. 그래서 3대의 컴퓨터를 새로 사고 제가 열심히 세팅을 했는데... 선배들이 사용해 보고는 4배가 빨라져야 하는데 1.5배 밖에 (클럭 속도 차이로 인한 향상) 안 빨라졌다고 하는 것입니다.

 ... 아... 진짜... 대학원 생활은 힘듭니다. 당연히 코드를 병렬로 작성하지 않아서 그런 거겠죠. 사실 이런 경우에 굳이 병렬 코드가 없어도, 이전에 1개 컴퓨터에 동시에 1개 실행하던 프로그램을 단순히 4개 실행해도 되기 때문에 병렬화 코드가 없어도 금전적인 이익은 매우 컸습니다. 100만 원 컴퓨터로 한 번에 1개 실행하던 거를 4개 실행할 수 있게 되었다는 건 컴퓨터 12개를 사는 대신, 3대만 사도 된다는 의미이니까요.

메모리가 많이 필요한 작업에선 이렇게 돌릴 수 없습니다만...

 

 굳이 이걸 이야기하는 이유는, 내가 실행해야 할 프로그램이 많다면 1개의 프로그램에 병렬화 루틴을 작성해서 여러 프로세서를 투입하는 게 아닙니라, 그냥 그런 프로그램을 동시에 여러 개 돌려도 된다는 것을 말하기 위해서입니다.

 사람마다 처한 상황은 다 다르고, 그때마다 적절한 병렬화 방법은 많이 다릅니다.

 

 아무튼 병렬 컴퓨팅을 크게 둘로 나누면,

 하나의 프로세스만이 실행되고, 여러 프로세서가 서로 메모리를 공유하는 방식과 (대표적으로 openmp, 멀티 스레드, CUDA)

 여러 프로세스가 실행되고 프로세스 별로 메모리를 공유하지 않는 방식입니다. (대표적으로 MPI, 멀티 프로세스)

 특히 컴퓨터가 여러대라면 당연히 컴퓨터별로 메모리를 공유할 수 없으니 후자를 사용해야 합니다.

 조금 더 들어가면 프로세스가 여럿이고, 각 프로세스에 프로세서가 여럿일 수도 있습니다만..

 프로세스 단위에서 생각하면 동일한 프로세스에 속한 프로세서 사이에서 메모리가 서로 공유되고 프로세스 간에는 메모리 공유가 안된다고 할 수 있습니다. 대신 정보 전달이 필요하다면, 프로세스 간에서 통신을 합니다. MPI라는 것은 메시지 패싱 인터페이스로, 서로 다른 프로세스 간의 통신 프로토콜입니다.

 

 제가 처음 사용한 병렬 프로그래밍은 MPI인데, 좀 어렵습니다. 개념적으로 이해해야 할 것도 있고, 기술적으로도 처리해야 할 것들이 많아서요. 특히 컴퓨터 간 통신도 해야 한다면 컴퓨터 세팅하기도 어렵고요.

 MPI는 여러 프로세스를 동시에 실행하고, 각 프로세스는 보통 1개의 프로세서를 가집니다.

 예에서 설명한 서로 독립적인 프로그램을 여러 개 실행하는 경우라면, 서로 간의 통신도 필요 없기에 굳이 MPI 같은 것을 사용할 필요도 없습니다. MPI는 수시로 통신이 필요한 경우에 사용하는 방식입니다.

 예를 들어 하나의 계산을 여러 개로 쪼개서 각 프로세스로 분배하면, 각 프로세스가 자신의 연산을 진행하고 그것을 하나로 결과를 취합한 후, 그것들을 다시 각 프로세스로 분배하고 다시 연산을 수행하는 경우에 사용합니다.

 혹은, 연산 도중에도 서로 다른 프로세스 간에서 필요로 하는 정보들을 통신을 통해서 가져올 수도 있습니다. 더 어려워 보이지만...

 통신이라 함은, 한 프로세스가 독점하고 있는 메모리에 저장되어 있는 것을 다른 프로세스가 볼 수 없기에, 다른 프로세스에 전달하기 위해 메모리 내용을 복사해서 전달한다는 의미입니다.

 

mpi처럼 동시에 여러 프로그램을 하나의 common world로 묶어서 실행할 수도 있지만, 부모 프로세스가 먼저 실행되고 자식 프로세스를 생성하는 식으로 작동하는 경우도 있습니다. (mpi도 어떻게 보면 그런 것 같긴 하지만... 모든 자식은 부모를 가지고 있으니까요.)

 

반면에 openmp는 메모리를 공유하는 방식의 병렬 프로그래밍 기법으로, 사용하기 정말 간단합니다.

 c 나 포트란에서 사용 가능한데, 거의 for 문 바로 앞에 1줄만 추가해 줘도 사용이 가능합니다.

 하나의 프로세스에 속한 프로세서들이 메모리를 공유하고 있기에 굳이 통신 같은 것이 필요 없습니다.

대신, 프로세스 간에 메모리가 공유되기에 서로 간섭할 수도 있어서 조심해야 합니다.

누구는 고기를 자르고 누구는 고기를 튀겨야 하는데, 자르지도 않은 고기를 튀기거나 자르고 있는 고기를 가져다 튀기거나, 튀겨진 고기를 자르거나 하면 안 되겠죠.

 

멀티프로세스 방식의 병렬 프로그램을 실행한 후 top 같은 명령어를 실행해 보면, 프로그램이(프로세스) 여러 개 실행되어 있고, 각각이 cpu 점유율이 최대 100% 정도임을 볼 수 있습니다. 

반면에, 멀티 스레드 방식의 병렬 프로그램은 하나의 프로그램(프로세스)만 실행되어 있고 그 프로세스가 수백%의 cpu 점유율을 차지하는 것을 볼 수 있습니다.

 

그런데... 문제는 python입니다.

python도 멀티스레드와 멀티프로세스를 지원하는데, 멀티 프로세스는 어차피 여러 프로세스가 실행되는 것이니 문제없습니다.

 문제는 python 멀티스레드입니다. 일단 python은 컴파일러가 아니라 인터프리터입니다. 즉, 코드를 계속 해석해 가면서 실행이 됩니다. 그런데, 멀티스레드를 사용할 때, 인터프리터가 동시에 1개밖에 사용할 수 없습니다.

global interpreter lock (GIL)이라는 놈이 있습니다.

python 멀티스레드는 하나의 프로세스에 여러 프로세서가 참여하는 방식인데, 문제는 프로세스 하나에 인터프리터가 1개뿐이라 여러 프로세서들이 인터프리터를 동시에 사용할 수 없습니다. 누군가가 사용하면, 다른 것들은 사용할 수 없도록 락이 걸려있습니다.

 python 특성상 인터프리터가 계속 작동하면서 프로그램이 실행되는데, 이 병목이 심하게 작용하므로 병렬화 효율이 매우 떨어집니다. 그래도 특정한 경우에는 사용할 수 있는 것 같지만... 인터프리터의 병목이 별로 없는 문제라거나... 혹시 다른 언어로 컴파일된 루틴들을 병렬로 실행하기만 할 때?... 잘 모르겠네요.

솔직히 대체 이런 거 왜 만들었는지 모르겠네요. 기술적으로 해결이 어려워서 해결을 못한 것인지...?

 아무튼 계속 해결하려고 하는 것 같긴 합니다만...

 

 현재로선 python에선 메모리 공유하는 방식의 멀티 스레드는 제대로 돌아가지 않는다...라고 생각하고 굳이 안 하시는 게 좋습니다.