python에서도 multiprocessing을 사용해야 하는 이유

4 분 소요

Intro - multiprocessing.cpu_count 값이 이상한데?

  • 최근에 간단하게나마 병렬프로그래밍을 공부하고 있습니다. multiprocessing 라이브러리를 사용해서, 간단하게 좀 건드려보고 있는데, 간단하게 아래의 코드를 실행해본 결과 제 CPU의 수가 8개라고 나오더군요.
  • 저는 i5 Quad-Core를 사용하고 있습니다. 네, CPU가 4개라는 이야기인데, 왜 여기서는 8개라고 나왔을까요?
import multiprocessing
multiprocessing.cpu_count() # 8
  • 역시 구글신은 모든 것을 알고 있습니다. 구글에 검색해본 결과 Stackoverflow - multiprocessing.cpu_count returning wrong number of cores라는 글이 나왔고, 이 글을 정리해보려고 합니다.
  • 처음에는 간단히 multiprocessing의 사용법을 정리해보려고 했지만, 결과적으로는 하이퍼스레딩, 프로세스, 스레드 등의 개념을 정리하게 되었습니다.
  • 이 글에서는 주요 개념들에 대해서 이해한 것을 작성하고, 다른 글에서 multiprocessing을 사용하여 병렬 프로그래밍하는 방법을 작성해보도록 하겠습니다.

Core 와 Processor는 다르다

  • 저는 쿼드코어 CPU를 가지고 있습니다. 즉, 일반적인 개념으로는 4개의 프로세서를 가지고 있다고 보면 됩니다만, 앞서 말한 것처럼 프로세서의 수는 8개라고 나오죠.
  • 다나와 같은 곳에서 CPU를 검색해보신 분은 이미 아시겠지만, 대부분의 CPU 스펙에는 ‘코어’와 ‘쓰레드’가 함께 작성되어 있습니다. 대부분, 4코어라면 8쓰레드라고, 8코어 라면 16쓰레드라고 딱 2배로 다 작성되어 있죠.
  • 네, 그렇습니다. 엄밀히 따지면 ‘코어’는 “물리적인 프로세서”를 말하고, ‘쓰레드’ 혹은 ‘프로세서’는 논리적인 개념을 말합니다.
  • 그리고 이를 더 설명하기 위해서는 ‘하이퍼스레드’라는 개념이 필요합니다.

Hyper-Threading(하이퍼스레딩)

  • 엄밀히 따지자면, “하이퍼스레딩”은 인텔사에서 개발한 SMT 기술(Simultaneous Multi-Threading)의 이름입니다. 하지만, 요즘에는 이 둘을 거의 구분하지 않고 사용하고 있는 것으로 보이죠.
  • Wikipedia - 하이퍼스레딩은 인텔에서 개발한 기술로, “하나의 코어(물리적 실행장치)에 두 개의 프로세서(논리적 실행장치)를 달아서 성능을 높이려는 전략”이죠. 이 기술이 어떻게 돌아가는가? 라고 의문을 가질 수 있을텐데, 그건 매우 설명하기 어렵습니다(네, 저도 아직 이해하지 못했다는 이야기입니다). 개념적으로는 그냥 하나의 CPU에 남는 리소스를 최대한 활용해서 어떻게든 2개로 나누어 버린 것이죠.
  • 따라서 이 기술을 사용하면, 쿼드코어이지만 마치 옥타코어인 것처럼 돌아가는 것을 알 수 있습니다. 아래 그림을 보시면 대략 이런거구나~ 라고 생각은 되지만, “왜 이렇게 되는데?”는 잘 모르겠습니다 호호.

SMT- simultaneous_multi_threading

  • 다만, CPU_A는 4개의 코어에 4개의 4개의 쓰레드를 가지고 있고, CPU_B는 4개의 코어에 8개의 쓰레드를 가지고 있다고 해봅시다. 이 때, 우리가 사용하는 프로그램이 만약 최대 4개의 쓰레드만 사용하는 프로그램이라면, 당연히 CPU_A가 더 빠릅니다.

프로세스와 스레드

  • 컴퓨터에서 어떤 프로그램을 실행하게 되면, 스토리지(하드 디스크)에 저장된 프로그램이 RAM으로 이동하게 됩니다. 이렇게 RAM에서 돌아가고 있는 프로그램을 보통 “프로세스”라고 부르죠. 그리고 각 프로세스에는 좀 더 작은 단위인 “쓰레드(Thread)”가 존재합니다.
  • 과거에는 “프로세스”를 곧 하나의 작업 단위로 동작시켰습니다. 하지만, 멀티태스킹 환경이 강화되면서 유저들은 다양한 프로세스들을 서로 오가는 일들이 많아졌죠. 이 때, 하나의 프로세스를 메모리에 올리기 위해서는(정확히 말하면 프로세스를 전환하기 위해서는), 꽤 큰 오버헤드가 필요하게 됩니다. 전문적인 용어로는 “컨텍스트 스위치의 오버헤드가 커져서” 라고 말하게 되죠.
  • 이처럼, 하나의 프로세스가 거대해짐에 따라서 발생하는 컨텍스트 스위칭 오버헤드를 감소하기 위해서 프로세스를 여러 개의 작은 단위인 ‘스레드’로 분할해서 동작하게 만들었습니다. 이로 인해 컨텍스트 스위칭 코스트가 감소하고 효율성이 개선되었죠.
  • 결과적으로 ‘스레드’는 “메모리에 적재되는 최소 작업 단위”정도로 해석하면 됩니다.
  • 다만, 요즘에는 ‘스레드’와 ‘프로세스’가 좀 혼동되어서 쓰이는 것처럼 보여요.

멀티 스레드, 멀티 스레딩

  • 스레드가 여러 개인 경우 “멀티스레드”라고 부르며, 이를 처리하기 위한 과정을 ‘멀티스레딩’이라고 부릅니다.
  • 100kg을 운반할 수 있는 사람 A와, 50kg을 운반할 수 있는 사람 B, C가 있다고 하겠습니다. 100kg에 가까운 일을 시켜야 한다면, 무조건 A가 더 효과적이겠죠(B, C의 경우 둘이 호흡을 맞춰야 하므로 더 느려질 수 있음)
  • 반대로, 50kg의 일들을 처리해야 한다면, 당연히 B, C와 일하는 것이 2배로 효율적입니다.
  • 다만, 앞서 말한 바와 같이, B, C간에 호흡에서 문제가 발생하지 않도록 Thread 스케쥴링을 진행해 줘야 하기 때문에, 추가적인 업무 분담이 들어가긴 하죠. 그리고 이는 프로그래머 입장에서 좀 까다로운 일입니다.

Processor의 수가 많을 수록 좋은가?

  • Processor, 즉 “프로세스를 담당하고, 이 프로세스 내에 존재하는 여러 스레드를 처리해주는 아이”가 많다고 해보겠습니다. 추가로, 만약 하나의 프로세스를 두 개 이상의 프로세서로 수행한다면, 해당 프로세스 내에 존재하는 여러 스레드들이 이 프로세서들로 분리되어 실행되겠죠.
  • 병렬 프로그래밍을 알지 못했을 때는, 이렇게 서로 다른 두 스레드드를 컴퓨터가 알아서, 인지하고 각 프로세서에게 분배해준다고 생각했었습니다. 하지만, 절대로 그렇지 않죠.
  • Processor의 수가 많다고 했을 때, 이 Processor들을 효과적으로 사용하기 위해서는 프로그램 개발 시에 항상 “병렬 프로그래밍”을 인지하고 개발해야 합니다.
  • 가령 “이 Thread와 이 Thread는 서로 독립적이므로 서로 다른 Processor에 핟당해줘도 됨!”과 같은 것을 인지하고, 프로그래밍한다면 CPU가 여러 개로 분할된 이점을 충분히 사용하겠지만, 그렇지 않을 경우에는 그다지 이득이 없을 뿐더러 오히려 손해를 볼 수도 있다는 이야기죠.
  • 만약, 단일 프로세스에서 하나의 스레드가 꽤 큰 컴퓨팅 파워를 필요로 한다고 하겠습니다(정확히는 프로그래머가 각 스레드를 그런식으로 구성해버린 것이죠). 그런데, 우리는 애매한 성능의 듀얼코어를 가지고 있다고 해보죠. 그렇다면 하나의 스레드를 처리할 때마다, 애매한 성능의 싱글코어만을 사용하게 됩니다. 만약 애매한 성능의 코어 2개가 아니라, 꽤 괜찮은 성능의 싱글코어를 가지고 있었다면 각 스레드를 더 빠르게 수행할 수 있었겠죠.
  • 하지만, 단일 프로세스에서 각 스레드를 적절하게 구분하였다면, 듀얼코어의 성능을 최대로 뽑아낼 수 있습니다. 두 프로세서가 스레드를 각자 처리해주니까, 보다 빠르게 프로세스를 끝내버릴 수 있죠. 물론, 늘 말하지만, 프로그래머는 존나 힘들 겁니다.
  • 다만, 요즘에는 하이퍼스레딩된 논리적 프로세서까지 포함하여 16개의 프로세서까지 나오고 있습니다. 이런 경우에, 하드웨어의 강점을 충분히 이용하려면 어우야, 프로그래머 갈려나가겠네요.

시분할 멀티 스레딩(Temporal Multi-Threading)

  • 일종의 시분할 기법으로, 스레드 A를 조금 수행하고, 스레드 B를 조금 수행하는 식으로 빠르게 변환해가면서 스레드가 동시에 돌아가는 것처럼 보이게 하는 기법을 말합니다.
  • 실제로 동시에 스레드가 돌아가는 것이 아니며, 한 번에 하나씩만 돌아가기 때문에, 싱글 코어 CPU에도 문제없이 적용할 수 있죠.
  • 물론, 이 방법은 멀티스레딩을 구현한다고 딱히 빨라지지 않습니다. 그저 아주 빠르게 두 프로그램 사이를 오가며 수행하는 것이기 때문에, 동시에 수행하는 프로그램의 수가 늘어날수록 각 프로세스(혹은 스레드)가 종료되는 시간은 더 길어지죠

동시적 멀티 스레딩(Simulateneous Multi-Threading)

  • 말 그대로, 동시에 두 스레드를 구현하는 기법을 말합니다. 사실 세부적인 방법은 이해하기 어려워서 쓰지 못했지만, 코어 내부에 2개의 가상 코어(정확히는 State)를 만들어서, 코어 자체를 뻥튀기하는 개념이라고 보시면 됩니다.
  • 이게 어떻게 가능한지는 음, 다음에 알아보도록 하죠 호호.

Wrap-up

  • 좀 갑작스럽지만, 일단은 좀 마무리합니다. 글이 너무 길어지기도 하고, 궁금한 것들을 확장해나가다보니 너무 내용이 늘어나거든요.
  • 정리하자면, 다음과 같습니다.
    • CPU의 코어수가 많을 수록 좋다. 하지만 물리적인 코어를 늘리는 것은 비용문제도 그렇고 한계가 있다.
    • 따라서, ‘하이퍼스레딩’을 이용해서 물리적인 코어의 상태를 분할하여 논리적인 코어를 만들었고, 이를 통해 프로세서의 수를 2배로 만듬.
    • 이로 인해, 동시에 여러 프로세스(혹은 스레드)를 돌려야 하는 경우 이득이 생긴다.
    • 다만, 이득이 생기려면, 해당 CPU 위에서 돌아가는 프로그램이 여러 프로세서들을 이용할 수 있도록 최적화되어 있어야 한다. 병렬적인 면을 전혀 고려하지 못하게 설계된 프로그램은 이 이점을 전혀 살리지 못한다.
    • 엄밀히 따지면 ‘프로세스’와 ‘스레드’는 다르다. 그러나, 요즘에는 이 개념들이 좀 혼재되어 사용되는 것으로 보인다.
  • python에서도 멀티프로세싱을 수행하기 위한 라이브러리로, joblib, multiprocessing과 같은 라이브러리가 있다. 이 아이들을 잘 사용하면, CPU를 최대한 사용할 수 있는 효율적인 프로그램을 만들 수 있다.

Reference

댓글남기기