>frhyme.code
•others

category:

Issue

  • 그럼, 엄밀히 따지면 프로세서가 아니라 “스레더”인 셈 아닌가.
  • 하이퍼스레딩은 일부러 하나의 물리적 프로세스가 멀티스레딩이 가능하도록 함으로써, 논리적으로 프로세서의 수를 뻥튀기 한 것에 가까움.
  • 멀티프로세싱과 멀티스레딩은 다르다. 그럼 그냥 Threader의 수가 8개이거나 해야지, 왜 CPU의 수가 8개로 잡히나?
    • 흠흠흠.
  • 인텔에서 개발한 하이퍼스레딩은 보안상 취약점들이 많이 존재함.
  • 멀티 코어 프로세서는 무엇인가.
  • 즉, 다음을 잘 정리해야 함
    • 멀티 코어
    • 멀티 프로세서
    • 멀티 스레딩

Intro - multiprocess.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가 있다고 하네요.
  • 조금 더 풀어서 말하자면, 사실 여기서 말하는 “코어”는 “물리적인 프로세서”인 것이고, 코드에서 말하는 “프로세서”는 “논리적인 프로세서”인 셈이죠.

Hyper-Threading(하이퍼스레딩)

  • 엄밀히 따지자면, “하이퍼스레딩”은 인텔사에서 개발한 SMT 기술(Simultaneous Multi-Threading)의 이름입니다. 하지만, 요즘에는 이 둘을 거의 구분하지 않고 사용하고 있는 것으로 보이죠.
  • Wikipedia - 하이퍼스레딩은 인텔에서 개발한 기술로, “하나의 코어(물리적 실행장치)에 두 개의 프로세서(논리적 실행장치)를 달아서 성능을 높이려는 전략이죠.
  • 따라서 이 기술을 사용하면, 쿼드코어이지만 마치 옥타코어인것처럼 돌아가는 것을 볼 수 있다는 이야기죠. 아래 그림을 보시면 대략 이런거구나~ 라고 생각은 되지만, “왜 이렇게 되는데?“는 잘 모르겠습니다 호호.

SMT- simultaneous_multi_threading

프로세스와 스레드

  • 컴퓨터에서 어떤 프로그램을 실행하게 되면, 스토리지(하드 디스크)에 저장된 프로그램이 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)를 만들어서, 코어 자체를 뻥튀기하는 개념이라고 보시면 됩니다.
  • 이해가 어렵다.

멀티 프로세싱

  • ’멀티 프로세싱’과 ’멀티 스레딩’이 구분이 좀 모호한 부분들이 있지만, 여기서는 전통적 의미의 ’멀티 프로세싱’에 대해서 잘 정리해보도록 하겠습니다.

대칭형 다중 처리

그 다방향 SMT 개념이라는 것부터 설명하자면 ’CPU 전체의 효율을 최대한 높인다’는 목표로 탄생한 개념으로써, 목표 자체만 보면 기존 멀티 CPU 시스템에서나 볼 수 있었던 SMT와 똑같지만 구현 과정이 다르고 무엇보다도 그동안 가격 문제 때문에 멀티 CPU를 접하기 어려워서 싱글코어 싱글스레드 CPU만 접하는 일반 사용자들도 SMT를 경험할 수 있다는 점은 기존의 SMT와 가장 큰 차이점이라고 볼 수 있다. 하드웨어 자체의 변화는 다방향 SMT로 동작하기 위한 별도의 하드웨어 모듈이 추가된 것 말고는 없으며, 코어 하나에 동시에 돌릴 스레드의 개수를 많이 우겨 넣어봤자 CPU 코어 하나의 최대 성능 이상은 절대 발휘할 수 없지만, 프로그램의 한계 때문에 쓰지 못했던 CPU 전체의 남은 성능을 끝까지 쥐어 짜는 셈이다.

코어 하나에 스레드 여러 개의 최초 구현체는 그나마 가장 간단한 양방향(2-Way) SMT로, 그 외에 4방향(4-Way) SMT와 8방향(8-Way) SMT도 존재하지만 하드웨어가 가지는 자원의 한계로 양방향 SMT가 현재까지 가장 많이 적용되고 있는 방식이다. 따라서 이 항목의 SMT는 양방향 SMT를 기준으로 서술한다.

논리적, 즉 S/W의 관점으로는 하나의 코어에 2개의 가상의 코어를 만들어서 CPU 2개로 인식된다. 기본적으로 별개의 명령을 처리해야 하기 때문. 그래서 양방향 SMT가 지원되는 CPU를 Windows XP 이후 버전의 작업 관리자에서 보면 코어 수가 2배로 뻥튀기 되는 것을 확인할 수 있다. 참고로 Windows NT 3.1부터 Windows 2000까지는 멀티 CPU 또는 멀티코어 CPU를 인식하지만 양방향 SMT까지 인식하지 못 하기 때문에 Windows XP부터 해당된다. DOS 커널 기반의 Windows 1.0부터 Windows Me까지는 멀티 CPU 또는 멀티코어 CPU조차 제대로 인식하지 못 한다.

예를 들어 1과 2라는 프로세스(스레드)에서 명령을 처리할 때 양방향 SMT를 지원하지 않는 CPU 의 경우, (는 다른 프로세스로 전환)

Wrap-up

  • 정리하자면, “병렬 프로그래밍을 위해서는 우선 내 CPU가 여러 개가 있어야 하고, 요즘에는 여러 코어가 있는 것이 기본이지만, Hyper-Threading을 이용해서 요즘에는 2배로 뻥튀기해버려서 그래픽 작업등을 할 때 훨씬 효과적이지만, 이 아이들을 제대로 굴려 먹으려면 내가 프로그래밍할 때 잘 분할해서 처리해줘야 한다”라는 이야기죠.
  • 이를 잘 사용하기 위한 방법으로 joblib, multiprocessing과 같은 라이브러리가 python에는 존재합니다.

Reference