하이퍼쓰레딩: 병렬성의 극대화 / 쓰레드 수준의 병렬성

Jin Hur·2021년 8월 22일
0
post-thumbnail

reference: "프로그래머가 몰랐던 멀티코어 CPU 이야기" / 김민장, "Computer System A Programmers'Perspective" / 랜달 E.브라이언트

하이퍼쓰레딩은 하나의 물리 프로세서가 두 개의 논리 프로세서로 보이게 하는 기술이다. 하이퍼쓰레딩은 슈퍼스칼라 프로세서에서 동시에 두 쓰레드로부터 명령어를 가져와 파이프라인에 넣는 기술이다. 이렇게 하면 제대로 활용되지 않는 프로세서 자원을 더욱 더 효과적으로 쓸 수 있다.
두 개 혹은 그 이상의 논리 프로세서를 보이게 할 수 있는 원리는 "쓰레드 문맥을 하드웨어 수준에서 복제"하기 때문이다. 게다가 하이퍼쓰레딩은 명령어 수준 병렬성(ILP)보다 단위가 큰 쓰레드 수준 병렬성(TLP)을 활용하는 방법이다.

하이퍼쓰레딩이란?

하이퍼쓰레딩(Hyper-Threading)은 인텔 펜티엄 4에 구현한 동시 멀티쓰레딩(Simultaneous Multi-Threading, SMT)기술의 상품명이다. 하이퍼쓰레딩은 물리 프로세서 코어는 하나인데 프로세서가 두 개처럼, 다시 말해 두 개의 논리 프로세서로 보이게 하는 기술이다. 하드웨어 수준에서 두 개 이상의 쓰레드가 동시에 실행되므로 하드웨어 쓰레드라는 표현을 쓴다.
하이퍼쓰레딩은 특별히 두 개의 논리 프로세서를 만들 뿐, 4개 혹은 그 이상의 논리 프로세서도 SMT 기술로 만들 수 있다. 그런데 이렇게 물리적으로 하나인 프로세서가 논리적으로 여러 개가 보일 수 있는 근본 원리는 쓰레드 문맥(context)을 하드웨어가 동시에 여러 개를 지원할 수 있기 때문이다.

하드웨어 멀티쓰레딩?

하드웨어 멀티쓰레딩은 여러 개의 쓰레드 문맥을 동시에 하드웨어가 관리하면서 여러 쓰레드를 실행시키는 기술이다.
현대 운영체제는 모두 멀티태스킹을 지원한다. 그런데 일반적인 싱글코어 프로세서는 주어진 시간에 오직 하나의 쓰레드만 처리할 수 있다. 이런 하드웨어에서 운영체제가 멀티태스킹을 구현하려면 프로세서 자원을 여러 쓰레드에 돌아가면서 빌려주는 쓰레드 스케줄링이 필요하다. 한 쓰레드가 자신에게 주어진 시간만큼 프로세서를 썼다면 운영체제는 이 쓰레드에서 프로세서를 회수하여 다른 쓰레드에게 준다. 이것이 문맥 교환이다. 프로세서는 한 번에 하나의 쓰레드 문맥을 처리할 수 있기에 문맥 교환을 구현하려면 명시적으로 이전 문맥을 어딘가에 저장하는데, 이때 비용이 발생한다. 무작정 쓰레드를 많이 만들어 이 쓰레드들을 바쁘게 돌리면 과다한 문맥 교환 비용으로 결코 높은 성능을 얻을 수 없다.

문맥은 바로 컴퓨터 구조적 상태(architectural state)를 가리킨다. 쉽게 말하면 현재 쓰레드가 수행 중인 레지스터 값이다.
결국 쓰레드 문맥의 핵심은 결국 레지스터 집합이다. 프로세서에 레지스터 파일이 하나만 있다면 레지스터 값은 문맥 교환 시 메모리로 잠시 쫒겨 났다가 다시 복구된다. 하드웨어 멀티쓰레딩은 쓰레드 문맥을 유지하는 레지스터를 복제하여 동시에 여러 쓰레드 문맥을 유지한다. 그러니 문맥 교환 비용이 거의 들지 않으면서 여러 쓰레드를 처리할 수 있다.
=> 문맥 교환 비용 감소

멀티쓰레딩이 효과적인 이유는 문맥 교환 비용을 줄이는 것보다 더 근본적인 이유가 있다. 먼저, 쓰레드 사이에는 매우 찾기 쉬운 병렬성이 존재한다. 명령어 수준 병렬성보다 더 높은 수준의 쓰레드 수준 병렬성을 이용하는 것이다. 쓰레드 간의 뮤텍스(mutex)나 임계영역(critical section)을 이용해 서로 직렬로 수행되어야 하지 않은 이상 여러 쓰레드는 완벽하게 독립적으로, 즉 병렬로 수행될 수 있다. 하드웨어 멀티쓰레딩은 TLP를 최대한 활용하는 방법이다.

또, 멀티쓰레딩은 자원의 효율성을 극대화하는 기술이다. 달리 말하면 에너지 효율도 높일 수 있다. 비순차 실행이 순차 실행보다 더 나은 성능을 보일 수 있었던 이유는 결국 아무 일도 하지않고 낭비되는 사이클을 줄일 수 있다는 점이다.

프로그램의 데이터 흐름을 분석해 처리하는 비순차 실행이 명령어 처리율이 좋지만, 그래도 제대로 활용되지 않는 파이프라인 실행 장치가 많을 수 있다.(ex, 파이프라인 이슈 슬롯 낭비, story09 참고) 이는 주어진 시간에 파이프라인에 투입할 명령어가 없어 낭비되었다는 뜻인다. 프로그램 자체가 가진 ILP(명령어 수준 병렬성)의 제한이나 기타 여러 문제로 낭비될 수 있다. 이런 것을 수평 방향 낭비라 한다. 그런데 어떤 사이클은 명령어를 하나도 넣을 수 없을 수 있다. 이것을 수평 방향의 낭비와 구분해 수직 방향의 낭비라고 한다. 예를 들어 임계 경로에 있는 명령어가 캐시 미스를 겪고 있고, 이때 찾을 수 있는 ILP가 없다면 완전히 놀 수 밖에 없다.

프로그램의 데이터 흐름을 분석해 처리하는 비순차 실행이 명령어 처리율이 좋지만, 그래도 제대로 활용되지 않는 파이프라인 실행 장치가 많을 수도 있다. 멀티태스킹 기술은 이런 낭비를 줄여준다.
특히 수직 방향의 낭비를 줄일 수 있는데 이 공간에 다른 쓰레드의 명령어를 가져와 넣는 것이다.
문맥 교환 비용도 줄이고 완전히 활용되지 않는 자원에 TLP를 얻어 적극적으로 사용하는 기법이다.


멀티쓰레딩 구현 방식

멀티쓰레딩은 구현 방식에 따라 몇 가지로 나뉠 수 있다. 여러 쓰레드로부터 명령어를 어떻게 읽을 것인가에 따라 미세 단위(Fine-Grained) 멀티쓰레딩, 큰 단위(Coarse-Grained) 멀티쓰레딩, 동시 멀티쓰레딩(Simultaneous)로 나뉜다.


source: https://www.semanticscholar.org/paper/Simultaneous-multithreading%3A-a-platform-for-Eggers-Emer/c9d6abd295a9428f249d33901275b7a67d0869cb/figure/0

미세 단위 멀티쓰레딩은 마치 운영체제의 비선점(preemptive) 스케줄링 기법(또는 비선점형 멀티태스킹)과 흡사하다. 이것은 일정 사이클 동안 번갈아가며 프로세서가 쓰레드로부터 명령어를 가져와 파이프라인에 넣는다. 일정 사이클 동안 번갈아가며 프로세서가 쓰레드로부터 명령어를 가져와 파이프라인에 넣는다. 가장 미세하게 만든다면 사이클마다 프로세서는 하드웨어 쓰레드를 돌아가며 명령어를 가져와 실행한다.
이러한 멀티쓰레딩은 수직 방향 낭비를 줄일 수 있지만, 그럼에도 쓰레드 A, B에서 어떤 사이클에 투입할 수 있는 명령어가 하나도 없다면 낭비는 계속 일어난다.

큰 단위 멀티쓰레딩는 정의에 따라 여러 가지가 될 수 있다. 하나가 아니라 여러 개의 사이클을 단위로 하여 스케줄링할 수도 있다. 그러나 어떤 쓰레드에서 캐시 미스 같은 일이 벌어져 명령어를 투입할 수 없을 떄 문맥을 다른 쓰레드로 전환하는 것이 일반적인 방식의 큰 단위 멀티쓰레딩 구현이다.
예를 들어, 쓰레드 A가 명령어를 계속 인출하다가 어느 순간 쓰레드 A가 캐시 미스로 더이상의 인출할 수 있는 명령어가 없다 하면, 프로세서는 준비되어 있는 쓰레드 B에 기회를 주어 프로세서를 사용할 수 있게 한다. 운영체제에서 볼 수 있는 스케줄링이 단순히 하드웨어에서 구현되는 것으로 볼 수 있다. (대신 앞서 말했듯 문맥 교환의 비용이 거의 발생하지 않는다.) (이러한 큰 단위 멀티쓰레딩은 nVidia의 GPU에서도 유사한 방식으로 탑재하고 있다.)
큰 단위 멀티쓰레딩은 특정 이벤트, 즉 레이턴시가 긴 명령어가 걸렸을 때 스케줄링이 일어난다 하여 Switch-On-Event 멀티쓰레딩이라고 부르기도 한다.

동시 멀티쓰레딩: 미세/큰 단위 멀티쓰레딩은 수직 방향의 낭비는 효과적으로 줄일 수 있지만 수평 방향의 낭비는 줄이지 못한다. 동시 멀티쓰레딩은 이 문제를 해결한다. 동시 멀티쓰레딩은 멀티쓰레딩 기술의 극단적인 형태로 매 사이클마다 여러 쓰레드에서 '동시에' 명령어를 가져와 파이프라인에 넣는다. 수평 방향의 낭비를 다른 쓰레드에서 명령어를 가져와 채운다. 물론 동시 멀티쓰레딩을 쓰더라도 수평/수직 낭비를 완벽히 제거할 수 있는 것은 아니다.

처음 SMT를 제안한 논문은 시뮬레이터를 이용하여 8-와이드 슈퍼스칼라(최대 8개의 명령어를 동시에 투입할 수 있음) 순차 방식의 프로세서에서 자원이 생각보다 많이 덜 활용됨을 주장했다. 그리고 이 문제의 해결책으로 SMT을 제시했다. 이 실험 결과를 간단히 요약하자면 단지 19%의 프로세서 자원만이 바쁘게 사용되었다고 한다. 나머지는 캐시 미스, TLB 미스, 메모리 로드 지연 시간, 명령어 사이의 의존성 등으로 낭비되었다. 만약 자원 효율을 갉아먹는 원인 중 하나가 모든 프로그램에 걸쳐 압도적이라면 그 부분만 잡으면 될 것이다. 예를 들어, 캐시 미스가 아주 큰 원인이라면 캐시를 늘리거나 정책을 개선하면 된다. 그러나 이 연구 결과에 따르면 각 프로그램마다 낭비되는 원인이 제각각 달랐다. 따라서 몇 개의 단순한 개선책으로 이 낭비를 크게 줄일 수 없다는 결론을 도출했다. 이런 이유로 SMT는 제안되었다.

인텔이 구현한 하이퍼쓰레딩은 하나의 물리코어를 두 개의 논리 프로세서로 보이게 한다. 이를 달리 표현하면 2-way SMT라고 쓴다. 그런데 하드웨어 멀티쓰레딩은 2개보다 많은 쓰레드 문맥도 얼마든지 유지할 수 있다.(2개보다 많은 레지스터 파일?) 또한 멀티쓰레딩 기술은 비순차/순차에 상관없이 구현될 수 있다. SMT가 구현된 프로세서는 인텔의 펜티엄 4, 네할렘(Nehalem) 계열 프로세서가 있으며, 저전력 프로세서인 아톰(Atom) 프로세서도 구현했다.


동시 멀티쓰레딩의 구현과 성능

SMT가 처음 구현이 시도된 프로세서는 동시에 8개의 명령어를 투입할 수 있는 슈퍼스칼라(8-와이드)에 4-웨이(way) SMT를 지원하는 구조였다. 그러나 이 기술을 설계한 인력이 결국 2001년 인텔로 매각됨에 따라 SMT는 인텔 펜티엄 4에서 하이퍼쓰레딩이라는 이름으로 구현될 수 있었다.

SMT가 가장 매력적인 것은 하드웨어 부담이 그리 크지 않으면서 동시에 여러 하드웨어 쓰레드 문맥을 유지할 수 있다는 점이다. 최근 프로세서의 가장 큰 설계 목표 중 하나는 에너지 효율이다. 아무리 높은 성능을 낼 수 있는 장치와 알고리즘이라도 전력을 많이 쓰면 발열을 감당할 수 없어 채택될 수 없다. 인텔은 SMT가 에너지 효율 측면에서도 큰 장점이 있다고 주장한다.

SMT는 추가적인 쓰레드 문맥을 유지할 수 있게 자원이 반드시 복제되어야 한다. 대표적으로 레지스터 파일이 있다. 그러나 이 레지스터 파일은 수억 개의 트랜지스터가 집적되는 현대 프로세서에서 결코 많은 양이 아니다.

비순차 프로세서에서 가장 중요한 명령어 스케줄링 장치와 실행 장치가 SMT에 거의 무관하다는 것이다. 전면적인 파이프라인 수정 없이 노는 자원을 더 써서 논리 프로세서 개수를 늘릴 수 있으니 매력적일 것이다. SMT 구현은 크게 자원의 복제, 분할(partition), 공유, 무관으로 나눌 수 있다.

인텔 Core i7의 마이크로아키텍쳐인 네할렘의 SMT 구현 정책

  • 복제(쓰레드마다 복제됨): 레지스터 파일, 레지스터 리네이밍 관련 장치, 큰 페이지 명령어 TLB
  • 파티션(쓰레드마다 정적으로 할당): 로드/스토어 버퍼, 리오더 버퍼(ROB), 작은 페이지 명령어 TLB
  • 경쟁적으로 공유(쓰레드의 행동에 따라 결정됨): Reservation Station, 캐시, 데이터 TLB, 2차 TLB
  • 무관(SMT에 영향이 없음): 비순차 실행 장치

위 구현 정책에서 중요한 레지스터 파일은 복제되지만 일부 자원, 예를 들어 로드/스토어 버퍼(story 15)는 하이퍼쓰레이딩을 켜면 정적으로 분활된다. 캐시와 TLB는 논리 프로세서가 서로 경쟁적으로 상황에 따라 쓸 수 있도록 한다.


SMT 이슈

동시 멀티쓰레딩 환경에서는 무엇보다 캐시와 비순차 실행 장치를 공유함에 따라 불균형이 야기될 수 있다. 특히 사이클마다 여러 쓰레드에서 명령어를 인출해야 하는데 어떤 규칙과 정책으로 뽑아낼지는 쉽게 결정할 수 없는 문제다. 예를 들어 3-웨이 슈퍼스칼라에서 2-웨이 SMT를 구현했다고 가정한다. 어떤 싸이클에서 두 쓰레드가 모두 2개의 명령어를 동시에 투입할 수 있다하면, 총 4개의 명령을 투입할 수 있는 상황이다. 그런데 가능한 하드웨어 자원은 3개밖에 없다. 그렇다면 어떤 식으로 스케줄링 해야 할까 하는 어려움이 있다. 이것은 대표적인 SMT 구현 문제 중 하나로서 SMT 프로세서의 명령어 인출 정책 중 대표적인 기법으로 ICOUNT라는 것이 있다. 각 쓰레드가 지금까지 처리한 명령어 수를 헤라이고, 명령어를 인출할 때 지금까지 가장 적게 명령어를 처리한 쓰레드에 우선순위를 주는 방식이다. 이 알고리즘은 거시적으로 보면 쓰레드의 비슷한 수의 명령어를 파이프라인에서 처리하게끔 균형을 맞춰준다.

만일 SMT로 만들어진 두 논리 프로세서에서 많은 캐시가 필요하다면 이 또한 해가 될 수 있다. 하이퍼쓰레딩과 같은 기술을 고려해서 캐시를 만들기란 쉽지 않다. 따라서 보통 캐시 장치는 SMT와는 무관하다. 최악에는 하이퍼쓰레딩으로 두 프로그램의 실행 시간이 비-하이퍼쓰레딩 기반의 프로세서보다 느려질 수 있다. 즉 두 논리 프로세서가 캐시 충돌을 자주 일으킨다면 성능이 저하될 수 있다.
그러나 꼭 단점만 있는 것은 아니다. 두 논리 프로세서가 캐시를 공유하므로 서로 좋은 효과를 낼 수 있다. 한 논리 프로세서가 미리 데이터를 캐시에 올려다 놓고, 운 좋게 나머지 논리 프로세서가 이것을 공유한다면 이득을 얻을 수 있다.

운영체제도 SMT를 인지하여 최적화하는 것이 필요하다. 하이퍼쓰레딩을 예로 들면, 물리 코어보다 두 배 많은 논리 프로세서를 운영체체는 보게 된다. 그러나 만약 운영체제가 이 논리 프로세서를 똑같은 물리 프로세서로 간주하고 스케줄링 하는 것은 바람직하지 않다. 특히 요즘 같은 멀티코어 환경이라면 더더욱 그렇다. 예를 들어, 쿼드코어 + 하이퍼쓰레딩이 지원되는 프로세서에서 쓰레드 2개가 바쁘게 움직인다고 가정한다. 운영체제가 하이퍼쓰레딩 여부를 알지 못해 하이퍼쓰레딩 관계에 있는 두 논리 프로세서에 쓰레드를 할당한다면 오히려 성능이 하락될 것이다. 가능하다면 다른 물리 코어에 있는(상위 레벨의 캐시를 공유하지 않는) 논리 프로세서에 쓰레드를 할당하는 것이 합리적이다.

SMT가 나올 수 있었던 문제의 원인은 매우 자명했고, 이 기술의 근본 원리도 상당히 직관적이었다. 잘 활용되지 않는 자원에 쓰레드 수준 병렬성(TLP)을 활용해 효율을 높이는 것이었다. 그러나 실제 프로세서에 구현할 때는 여러 가지 구현 문제점이 있음을 알았다. SMT는 평균적으로는 효율적으로 높은 성능을 가져다주었지만, 공유하는 자원이 많기에 불균형을 일으킬 수 있다는 문제가 있었다.


결론

하이퍼쓰레딩이라는 이름으로 친숙한 SMT 기술은 슈퍼스칼라 프로세서에서 제대로 사용되지 않는 프로세서 자원이 생각보다 많다는 관찰에서 출발하였다. SMT는 기존에 있었던 하드웨어 멀티쓰레딩을 확장하여 매 사이클마다 여러 쓰레드에서 명령어를 뽑아 프로세서 자원의 사용률을 높였다. 또한, 명령어 수준 병렬성(ILP)보다 큰 범위인 쓰레드 수준의 병렬성(TLP)을 활용하여 병렬성의 극대화로 볼 수 있다.

SMT는 하드웨어 쓰레드 문맥을 유지할 수 있는 최소한의 자원(ex, 레지스터 파일)만 복제해도 복수 개의 논리 프로세서를 얻을 수 있었다.

그러나 캐시와 실제 실행 장치는 논리 프로세서 사이에 공유되므로 자원의 효율적인 분배라는 또 다른 문제가 생긴다. SMT는 모든 프로그램에서 항상 좋은 결과를 내는 것이 아님도 알았다.

0개의 댓글