병렬처리의 한계와 가능성 – Minsky의 모순성

P개의 프로세서들을 사용한 병렬컴퓨터에서 프로세서들 간의 정보 교환에 따른 통신 오버헤드 때문에 시스템 성능은 P배가 아닌 log2P배까지밖에 개선되지 못한다는 주장이다.

이런 주장은 1960년대에 적은 수의 프로세서들로 구성된 병렬처리시스템들을 이용하여 연구한 결과에 근거한 것이었다. 그러한 시스템들은 근대적인 운영체제를 사용하였는데, 프로세서의 수가 조금만 증가하여도 그에 따른 오버헤드가 매우 높아졌다. 통신 오버헤드가 커지면 프로세서의 이용률이 떨어지고 그것이 성능 저하의 주요 요인이 되었다.

그러나, 이후에 프로세서의 이용률을 높일 수 있는 여러 가지 방법들이 개발되었다. 우선, 더 효율적인 병렬 알고리즘들과 상호연결망들이 개발되었다. 또한 어떤 한 프로그램이 시스템 내의 모든 프로세서들을 이용하지 않는 경우에는, 남아있는 프로세서들이 다른 프로그램들을 처리하도록 해주는 스케쥴링 기술도 개발되었다. 이러한 기술들은 Minsky가 고려하지 못한 것들이었다.

물론, 통신 오버헤드 때문에 P개의 프로세서들을 가진 시스템의 성능이 단일프로세서 시스템 성능의 P배는 되지 못한다. 그러나 최근에 개발되고 있는 시스템들의 성능이 Minsky가 예측한 것보다는 훨씬 더 높아진 것에 대해서는 사실이다. 그로 인해 이 비관적인 예측 또한 어느 정도 빗나가게 되었다.

병렬처리의 한계와 가능성 – Grosch의 법칙과 VLSI

다단일 프로세서의 계산 속도는 물리적인 특성에 의하여 한계점을 가지고 있다. 실리콘 칩 상에 전자 프름의 최대 속도는 및의 속도의 1/10 정도(3*10^7m/sec)이다. 따러서 지름이 3cm인 칩에서 전기 신호의 최대 전송시간은 10^-9sec가 된다. 한 번의 신호 전송에 의해 한 번의 부동소수점 연산이 가능하다고 가정하더라도 그러한 칩으로 만들어진 컴퓨터의 계산 능력은 10^9FLOPS = 1GFLOPS가 된다,. 즉, 초당 10억번의 부동소수점 연산 처리까지만 가능하다. 다만, 최근에는 프로세서 칩 내부에 여러 개의 명령어 실행 유니트들을 두는 슈퍼스칼라 기술을 이용함으로써 프로세서의 처리 속도를 좀 더 높여뒀지만(이미 적용되어 나오는 프로세서들이 대부분임), 명령어-수준 병렬성에 의해 한계를 가진다. 

이러한 물리적인 한계를 극복할 수 있는 기술이 병렬처리이다. 병렬처리 개념이 문헌에 처음 소개된 것은 1920년대였으나, 상당 기간 동안 구체적인 연구와 개발이 이루어지지는 못한 데는 몇 가지 비관적인 예측들이 있었기 때문이다. 이것들을 하나 하나 살펴볼 것이다.

우선 살펴볼 것은 프로세서의 성능이 가격의 제곱에 비례하여 높아질 것으로 예측한 Grosch의 법칙이다. (p.s. 이 이론 자체가 엄청난 고전이지만, 병렬컴퓨터 구조론에 있어서 한번 지나가야 하는 법칙이다.) 이 법칙은 n개의 프로세서들을 사용하면 최대 n배의 성능 향상을 얻을 수 있지만, 루트n배의 비용만 투자해도 n배 더 빠른 프로세서를 개발할 수 있을 것이므로 단일 프로세서의 시스템으로도 같은 수준(n배)의 성능을 얻을 수 있다는 주장이다. 만약 이 법칙이 사실이라면, 굳이 많은 비용을 들여서 다수의 프로세서들을 이용한 병렬처리 시스템을 구성하는 의미가 없어지는 것이다. 그래서 당시의 이러한 논리 때문에, 오래동안 병렬처리 기술은 비용보다도 신뢰도를 더 중요시하는 군사용 또는 항공우주용 컴퓨터에서만 사용되어 왔는데, 병렬처리 구조가 결함 허용을 위한 하드웨어 중복성을 구현하기가 용이하기 때문이었다. (망가져도 다른 코어 쓰면 되니깐)

그러나, VLSI 칩들이 등장하면서 상황이 완전히 바뀌게 되었다. VLSI 기술을 이용하여 개발한 프로세서들은 대량 생산이 가능하기 때문에 가격이 저렴해지고, 따라서 약간의 비용만 추가하면 많은 수의 프로세서들을 포함하는 시스템을 구성할 수 있게 된 것이다. 결과적으로 Grosch의 법칙은 VLSI 기술의 출현과 함께 더 이상 성립되지 않게 된 것이다.

최근에는 가격보다는 처리 속도를 더 중요시하는 경향이 있기 때문에 많은 비용을 투자하면서 더 빠른 프로세서를 개발하고, 그러한 프로세서들을 많이 사용하여 더 높은 성능의 시스템을 개발하고 있다.

병렬 프로그래밍과 시스템 의존성

병렬 프로그래밍을 하면서 대부분 일단 배우는 것이 쓰레드를 잘 이용하는 방법부터 시작하여 하나하나 공부해 나갑니다. 저도 그런 수업을 들어왔고요. 그러다 여러 병렬 프로그래밍 환경을 살펴보면 은근 의존성을 겪게 됩니다.

실제로 프로그래밍을 병렬화 작업을 진행하려면 해당 라이브러리를 이용하여 작성하는 경우가 많은데, 이 라이브러리들이 시스템 의존적인 라이브러리가 있고 그렇지 않은 라이브러리가 있습니다. 대표적으로 엔비디아틔 쿠다(CUDA)와 같이 프로세서 제조사에서 지원하는 라이브러리를 이용해야 하는 경우도 있고, 라이브러리에서 좀 더 자율적으로 처리해주는 OpenCL, MPI와 같은 경우도 있습니다. 또한 인텔처럼 자사의 페러럴 스튜디오를 통한 병렬 프로그래밍을 좀 더 최적화하여 진행하도록 컴파일러도 제공합니다. 언어 레벨에서 지원하는 라이브러리도 늘고 있어서 병렬화 작업이 많이 친숙하게 다가오고 있는 것은 확실합니다.

일단 이런 식으로 살펴는 봤는데, 그럼 이것들의 경우에는 직접 제어하는 프로세서, 시스템에 얼마나 의존적일수록 좋은 것일까라는 생각을 해봅니다만…

가장 단순한 사고를 해본다면, 저수준의 병렬화를 쉽게 구현할 수 있다면 확실히 빠릅니다. 라이브러리 함수 콜(API 함수 사용)을 통해 진행되는 병렬화에 의존하지 말고 직접 pthread를 능숙하게 제어하는 쪽이 퍼포먼스는 확실히 빠르게 나옵니다. 단, 저수준의 병렬화를 진행한다는 것은 정말 어려운 작업입니다.

그러나, 그렇게 하더라도 병렬화 작업을 위한 멀티코어 혹은 매니코어 제조사에서 제공하는 최적화된 API 라이브러리를 이길 수는 없습니다. 엔비디아의 쿠다만 하더라도, 쿠다에 최적화된 코딩 스타일이 존재합니다. (쿠다 프로그램밍을 해보시면 이게 어떤 뜻인지 대충은 아실 겁니다.) 인텔의 경우에도 CPU의 병렬화만 진행하더라도 패러럴 스튜디오에서의 병렬 최적화 튜닝 기법을 이용하면 일반 프로그램에서 쓰레드 제어를 잘 하는 것보다 훨씬 빠른 병렬화가 가능합니다.

이런 식으로 본다면 시스템에 상당히 의존적인 부분이 크게 느껴질 겁니다. 하지만, 응용 프로그램을 개발하는 사람들의 입장에서 보면 자신이 혹은 사용자가 어떤 시스템을 이용할 지 모르는데 시스템 의존적인 병렬 프로그래밍을 할 수는 없습니다. 상각해 보죠. 자신이 프로그램을 개발해야 하는 입장에서라면..? 여러 곳에서 이용하고 싶기 때문에 의존성이 최대한 낮은 쪽을 이용해야 여러 플랫폼, 여러 시스템에 상관 없이 이용할 수 있을 것입니다. (JVM 상에서 멀티쓰레드 제어에 침흘리는 사람들이 이런 입장일 겁니다.)

하지만 실제로 의존성을 무시할 수는 없습니다. 의존성에 의한 퍼포먼스 증가는 무시할 수 없는 수준입니다. 과학 실험을 위한 프로그램들은 대다수가 특정한 HPC를 정해놓고 이용하기 때문에 의존적인 프로그램으로 실험을 하여도 아무 문제가 없습니다. 과거에 제가 개발했던 열역학 시뮴레이터도 그랬고요. 하지만 응용 프로그램을 개발하는 입장에서 병렬 프로그램을 이용해야 한다면 특정한 시스템에 의존성을 가져야 되는지 아닌지에 대해서는 여러모로 고민하게 될 거 같습니다만, OpenCL이나 MPI를 보다보면 어느 정도의 해답은 있을 것 같습니다.