티스토리 뷰

프로그래밍

하이퍼쓰레딩[SMT]

과정 2017. 3. 11. 10:52


인텔의 프로세서에는 하이퍼쓰레딩이 있다.


하이퍼쓰레딩은 보통 SMT(Simultaneous multithreading) 라고 부르는데, 하이퍼쓰레딩은 인텔의 마케팅 용어라고 보면된다.



1. CPU 파이프라인



CPU가 맨처음 만들어질때 CPU가 작동되는 순서는 아래와 같다.


명령어인출(Fetch) - 명령어해석(Decode) - 명령어실행(Execute) - 기록(Writeback)


(사이클을 고려해야 하지만 일단 모든 과정은 1사이클(1Hz)이라고 가정하자)


램에 올라온 프로그램의 명령어 하나를 CPU가 실행하기 위해서는 4사이클(4Hz)이 걸린다는 이야기다.


F-D-E-W 저 네가지 순서를 거치는동안 하나의 과정을 제외한 모든 과정은 놀고 있게 된다.


그래서 각 파이프라인이 놀지 않도록 파이프라이닝화 했다. 이때문에 CPU 작동되는 순서에 Pre-frech라는 과정이 추가된다.


Pre-fetch는 사실 Fetch와 동일한데, Fetch되기전 미리 명령을 불러오는거다.


왜 미리 불러오는가? 왜냐 하면 다른 명령을 또 파이프라인에 넣어야 하기 때문이다.


하지만 파이프라이닝은 명령을 동시에 수행 할 수 있는 슈퍼스칼라와 다른데, 슈퍼스칼라는 FDEW 이런 파이프라인이 있을때, F 파이프라인에서도 여러개의 명령이 실행 될 수 있다. 즉, 명령이 특정 파이프라인을 점유 하고 있을때, 다른 명령은 점유 하고 있는 파이프라인 이외 것만 사용 할 수 있는 파이프라이닝과는 다르다.


➀ MOV RAX, PTR DWORD [RSP + 4]

➁ ADD RAX, RBX


자 위에 아주 단순한 프로그램 명령이 2개 있다.


파이프라이닝없이 위 프로그램이 수행된다면 아래처럼 총 8사이클이 걸릴 것이다.


F

D

E

W









F

D

E

W


하지만 파이프라이닝이 있다면, 아래처럼 총 5사이클로 줄어든다.


F 

D

E

W



F

D

E

W


엄청 줄었다. 그리고 저 과정을 더 길게 하면, 더 많은 명령을 빠른 시간안에 처리 할 수 있다.


하즈웰/스카이레이크의 경우 16개의 파이프라인이 사용되는데, 모든 파이프라인이 1사이클이라고 가정할때,


17사이클안에 2개의 명령을 수행 할 수 있다! 또한 파이프라인이 길어지면 CPU 클럭을 높일 수 있다고 한다.


하지만 저 파이프라이닝에는 엄청난 문제점들이 있다. 많지만 먼저 데이터가 다른 명령에 의존성이 있다면 문제가 된다.


그말이 무엇이냐 하면 100번지 메모리에 데이터를 기록하기 전에, 다음 명령이 100번지 메모리를 읽어가는 수가 있기 때문이다.


당장 저 명령을 봐도, RAX 레지스터가 RSP + 4메모리로부터 데이터를 읽어 들이기전에, RAX 레지스터와 더하기를 수행 하고 있다.


이 때문에 파이프라이닝을 하는 CPU는 올바르게 데이터가 처리 될 수 있도록 명령어를 예측하는 회로가 있다.


파이프라이닝의 두번째 문제, 예측을 예측일뿐, 만약 예측 분기가 빗나가면 그것 저 파이프라인을 통해서 나온 데이터를 모두 취소하고,

다시 명령을 불러오고, 예측하고 파이프라이닝을 수행 해야 한다.


이것 나름대로 엄청난 CPU 사이클이 소요된다.


당장 위에만 봐도 5사이클을 수행했는데, 예측분기가 실패해서 5사이클을 다시 실행 해야 해서 총 10 + a 사이클이 수행되기 때문에,

만약 파이프라인이 더 길어진다면 더 많은 사이클을 소비한다.


하지만 정말 분기예측을 잘 할 수 있다면, 이만큼 성능을 내기 좋은것은 없다!


실패의 예로는 펜티엄4 프레스캇은 40개가 넘는 파이프라인에 비해 분기 예측 캐시가 턱없이 작아서 매번 분기 예측에 실패했다.


그리고 엄청난 발열과 분기예측 실패로 인한 저조한 성능까지 얻었다.


발열은 예측 분기 실패로 인해서 많은 파이프라이닝 회로가 작동되었다는것을 의미한다.


번외로 인텔 CPU에서는 파이프라이닝이 효과를 내기 어려운데, 왜냐 하면 인텔의 x86 CPU 명령어가 가변길이를 가지기 때문인데,


왜냐하면 메모리로부터 데이터를 읽었을때, 이 데이터가 어떤 명령을 수행 하는 지 알 수 없기 때문이다.


근데 이건 AMD가 예전에 CICS -> RISC로 명령을 내부적으로 변환하는 방법을 도입하면서, 딱히 문제 될것은 아닌듯


(그래서 인텔이 이 명령어 체계를 갈아 엎을라 그랬는데... IA64가 망하고... AMD는 AMD64라는 IA32를 64비트 확장으로만 내놓고...

이러니 64비트가 되도 성능 향상이 없다.  AMD가 죄다..)


그래도 RISC보다야 효율이 떨어진다



2. 하이퍼쓰레딩



무슨 하이퍼쓰레딩을 이야기 하려고 저리 긴 이야기를 설명 했느냐


바로 하이퍼쓰레딩이 인텔이 펜티엄4에서 엄청 길어진 파이프라이닝을 효율적으로 써보자고 해서 쓴 기술이니깐!


나도 듣던바로는 인텔도 2002년도 펜티엄4를 초기에 만들때 클럭을 AMD보다 엄청 높혀보자고 연산 유닛은 다 쳐내고 파이프라인을 졸라 늘렸는데,


그래도 이렇게 길어진거 리스크도 크고 해저드도 많이 생길것 같아서 하이퍼쓰레딩 넣어봐야  할것같아서 넣긴했는데, 마이크로코드 상으로만 막은거라고 한다.




우선 쓰레드(Thread)라는것이 무엇인지 알고 가자, 쓰레드는 컴퓨터에서 가장 작은 논리적인 작업 단위를 의미한다.


CPU에서는 하나의 CPU가 하나의 쓰레드만 처리할 수 있지만, 하이퍼쓰레딩은 하나의 CPU가 두개 이상의 쓰레드를 처리하게 한다.


그것을 어떻게 가능하게 할까?


파이프라이닝에서 파이프라인이 무조건 작동되는건 아니라는것을 알게되었고,


놀고 있는 파이프라인을 더 사용해보자.


파이프라인이 제대로 사용될 수 없는경우가 있다.


데이터가 독립적이지 않는경우와 분기예측 실패났을경우가 대표적이다.

(추가로 메모리에서 데이터를 불러오는경우 메모리가 불러오기까지 파이프라프라인이 놀고 있기도 한다.)


데이터가 독립적이지 않을경우, 데이터가 독립된것이 보장됬다면 충분히 사용할 수 있다.


이 쓰레드는 다른 쓰레드와 전혀 다른일을 하게 된다.


이말은 다른 쓰레드끼리는 명령어가 사용될 데이터가 서로 독립적이라는거다.

(단, 메모리상 데이터 주소만 다르다)


위에서 사용했던 프로그램을 다시 가져와보면


➀ MOV RAX, PTR DWORD [RSP + 4]

➁ ADD RAX, RBX

쓰레드 A


➀ MOV RAX, PTR DWORD [RSP + 4]

➁ MOV RCX, PTR DWORD [RSP + 8]

➂ DIV RCX

쓰레드 B


이미 쓰레드 A에서는 1번 과정이 수행되고 있는과정이라 가정하고, 쓰레드 B에서는 3번 과정이 수행 되고 있는 과정이라고 가정하자


쓰레드 A 1번 과정은 RSP + 4위치의 메모리를 4바이트 읽어들여서 RAX 레지스터에 올리는 명령이고


쓰레드 B 3번과정은 RAX 레지스터의 값과 RCX 레지스터의 값을 나누어서 RAX 레지스터에 저장하라는 명령이야


쓰레드 A, B 둘다 쓰레드내 프로그램은 데이터가 다른데이터에 의존적이지만, 각 쓰레드끼리는 데이터가 서로 영향을 미치지 않아

(IPC... IPC... IPC... 는 어디에 팔아먹은걸까..)


쓰레드 A에서 1번과정에서 메모리에서 데이터를 불러오는동안, 쓰레드 B에서 3번과정은 데이터를 계산하고 있으면 되겠지?


고로 아주 조금이라도 동시에 두가지 일을 하는것처럼 보이는거다.


근데 사실 그냥 명령어 단위의 파이프라닝과 다를바 없어보이지만, 작업 단위인 쓰레드간 데이터는 서로 데이터가 독립적이라는것을 사용한 최고의 방법이 아닐까 싶다.



3. 왜 하이퍼쓰레딩을 켜면 CPU 코어보다 2배 많은 CPU로 보일까?


답은 쓰레드끼리는 메모리상 데이터가 독립적이기 때문이다.


좀 원초적인 이야기로 가자면 CPU는 원칙적으로 하나밖에 못하기 때문에, 여러 쓰레드를 번갈아가면서 처리한다. 그것도 매우 빠르게


그래서 사람이 느끼기엔 동시에 처리되는것처럼 보이는데, 사실 이 멀티태스킹을 구현하기 위해서 20세기 중반부터 사람들이 엄청 고민을 했다.


바로 서로 다른 프로세스(또는 쓰레드)가 서로 다른 메모리 공간에 있어야 하지 않을까?


이게 왜 문제냐면 프로세스(또는 쓰레드가) 완전히 서로 다른 메모리 주소를 쓴다면 상관이 없다.


하지만 이 서로 다른 주소를 쓰게 하려면 매번 프로그램을 다시 컴파일해야한다는것이다.


마치 크롬을 켰을때, 포토샵을 켠다면 서로 주소가 충돌하니 크롬이 사용하지 않는 메모리 주소로 바꿔서 다시 컴파일해서 실행해야 한단 말이된다.


하지만 CPU 내부에 MMU(Memory Management Unit)이라는 회로가 만들어지면서 이 문제는 해결 됬다.


각 프로세스마다 사용할 논리 메모리 주소 - 물리 메모리 주소를 테이블로 만들고, MMU는 이걸 참조해서 논리 메모리 주소를 물리 메모리 주소로 바꾸도록 CPU한테 뻥을 친다.


그러니깐 크롬의 100번지 메모리 주소나, 포토샵의 100번지 메모리 주소는 서로 다른 물리 메모리 주소를 가르킨다.

(근데 이게 무려 16비트 리얼모드에선 없었다.. 그래서 그당시 DOS 프로그램끼리 충돌 자주났던거다..)


인텔의 경우 OS가 기동될때 인텔각 코어의 MMU에 GDT(Global Desciptor Table)/IDT(Interript Descriptor Table)를 지정해주면서, CR0의 31번 비트를 1로 세트 하기 되면서 움직이게 된다.


다시 하이퍼쓰레딩으로 돌아오자.


하이퍼쓰레딩은 메모리상 데이터가 독립적인 쓰레드를 하나의 코어 파이프라인에 넣기 위한건데, 코어는 독립된 쓰레드끼리의 논리 주소를 실제 물리 주소로 바꿔야 하는 MMU가 두개 필요하게 된다.


결국 기존의 호환성은 유지하면서 성능은 향상 해야 하니 OS한테 CPU가 여러개 존재한다고 알려야 각 쓰레드의 물리 메모리 주소를 알 수 있게 되지 않을까?

이건 추측이다.


여담으로는 IBM PowerPC CPU의 경우 코어당 6개의 논리 쓰레드가 보이게 된다고 한다.

코어 2개만 있어도 12쓰레드. 작업관리자가 꽉찬다.

(출처- OS 공룡책)


4. 하이퍼쓰레딩은 무조건 좋을까?

답은 No.


하이퍼쓰레딩은 CPU의 많은 유닛중 놀고 있는 부분이 없게 만들자는 취지이기 때문에, 무조건 빨라지지 않는다.


예를 들면 IO-bounded한 쓰레드(메모리 입출력, USB 입출력, SATA 입출력, PCI 입출력, 네트워크 입출력)만 있거나


CPU-bounded한 쓰레드(게임과 같이 연산만 잔득 돌리는경우) 이런 경우에는 별 효과가 없다.


왜냐하면 어차피 CPU에 박혀잇는 유닛갯수는 같이 때문이다.


하지만 확실히 CPU-bounded한 쓰레드와 IO-bounded한 쓰레드가 같이 있다면, 분명 효과가 있다.


게임 방송처럼 게임을 하면서 영상 코딩, 네트워크 IO가 같이 일어날경우 어느정도 많은 CPU를 사용 할 수 있다.


또한 하이퍼쓰레딩의 단점으로는 쓰레드를 파이프라인에 잘 분배하도록 하는 회로가 있기때문에, 발열량이 증가 한다.

댓글