Skip to main content
CPU 제조사들은 성능, 전력 사용, 다이 면적 사이의 균형을 지속적으로 조정합니다. 이는 고객의 사용 패턴과 우선순위가 다르기 때문입니다. 데스크톱 사용자는 성능에 더 관심을 두는 반면, 노트북 사용자는 배터리 수명에 더 관심을 둡니다. 다이 면적이 작을수록 생산 원가를 낮출 수 있습니다. 이러한 상충하는 요구를 해결하기 위해 CPU 제조사들은 시스템이 비용 효율을 유지하면서도 현재의 워크로드 수요에 동적으로 적응할 수 있게 하는 _이기종 아키텍처_를 설계합니다. CPU 제조사들은 서로 다른 효율 수준을 갖는 CPU 코어를 사용해 이기종 토폴로지를 구현했습니다. 이 접근은 기존 CPU 아키텍처의 일부 코어를 더 전력 효율적인 코어로 대체합니다. 사용자가 CPU의 최대 성능을 필요로 하지 않는 경우 OS는 더 성능이 좋은 코어를 끄고 상당한 양의 전력을 절약함으로써 배터리 수명을 늘립니다. 더 많은 성능이 필요할 때는 OS가 성능 좋은 코어를 동적으로 켜서 늘어난 워크로드를 처리할 수 있습니다.

하드웨어

여러 독립 하드웨어 벤더(IHV)가 이기종 CPU 토폴로지를 다르게 정의합니다. Intel과 Qualcomm은 PerformanceEfficiency라는 용어를 사용하고, AMD는 PerformanceCompact를 사용합니다. 각 벤더는 코어 토폴로지를 설계할 때 성능과 효율성 간의 절충안을 만듭니다. 다음 예시는 관련된 설계 선택 중 일부를 보여줍니다.
  • 다른 캐시 크기
    • 서로 다른 코어들이 서로 다른 캐시 크기를 가질 수 있습니다.
    • 예를 들어, 성능이 좋은 코어는 더 큰 L1 및 L2 캐시를 가질 수 있습니다.
  • 캐시 공유
    • 코어 간에 캐시 공유 수준이 다양할 수 있습니다.
    • 예를 들어, 고성능 코어는 각각 프라이빗 L2 캐시를 가지고 있는 반면, 효율 코어는 단일 L2 캐시를 공유할 수 있습니다.
  • 대칭 다중 스레딩(SMT) 지원
    • 일부 코어는 SMT를 지원하고 다른 코어는 지원하지 않을 수 있습니다.
    • Intel은 SMT를 _Hyper-Threading_이라고 부릅니다.
  • 내부 코어 레이아웃
    • 다양한 코어 유형의 내부 자원이 다를 수 있습니다.
    • 예를 들어, 성능이 좋은 코어는 더 많은 부동 소수점 파이프, ALU 파이프, 또는 더 넓은 데이터 레인을 포함할 수 있습니다.
  • 최대 주파수
    • 코어 유형 간 최대 주파수가 다를 수 있습니다.
    • 예를 들어, 성능이 좋은 코어는 효율 코어보다 더 높은 주파수를 달성할 수 있습니다.
이러한 변화는 CPU 내 코어 전반에 걸쳐 성능과 전력 소비의 균형을 잡기 위해 설계되었습니다. 주파수가 높은 코어는 전력 사용 증가라는 비용을 감수하고 성능이 높습니다. 여러 코어가 모두 동일한 캐시를 공유하면 다이 크기가 줄어 전력 사용이 감소하지만 성능이 감소하는 대가가 따릅니다. 목표는 성능과 전력 사용에 대해 더 동적인 선택을 가능하게 하는 것입니다. 사용자가 최대 성능을 필요로 하지 않는다면 OS는 전력 소비를 줄이기 위해 더 효율적인 코어로 전환합니다. Windows는 CPU의 모든 코어가 동일한 명령어 집합 아키텍처(ISA)를 지원해야 한다는 요구 사항이 있음을 유의해야 합니다. 이는 CPU가 특정 명령어 집합을 지원한다면 CPU의 모든 코어가 그 집합을 지원함을 의미합니다. 예를 들어 CPU가 AVX2를 지원한다고 하면 모든 코어가 AVX2를 지원합니다. 성능 코어와 효율 코어의 차이에 대한 구체적인 내용은 각 IHV의 공식 문서를 참조하세요.

성능 수치

효율 수준이 다른 코어는 서로 다르게 동작할 것으로 예상됩니다. 하나는 성능을 위해, 다른 하나는 전력 절감을 위해 설계되었습니다. 문제는 코어들이 얼마나 다른가입니다. 여기 표시된 데이터는 IHV 전반에 걸친 종합입니다. 각 IHV는 자기 토폴로지에 대한 선택에 따라 각 테스트에 대해 다른 스케일링을 가집니다. 정확한 수치는 다르지만 패턴은 모든 IHV에서 유사합니다. 또한 각 CPU 세대는 다른 절충안을 만들어 상대적 수치를 조정합니다. 기존의 모든 IHV와 세대에 대한 수치를 만드는 것은 이 글의 범위를 벗어납니다.

합성 테스트

벡터 수학은 게임 타이틀이 한 프레임 내에서 수행하는 작업의 큰 부분입니다. 이 워크로드는 물리, AI, 렌더링 등 무엇이든 될 수 있습니다. 게임은 단일 128비트 SSE 레지스터에 들어맞는 세 개 또는 네 개의 float으로 구성된 벡터 타입을 사용해 이 작업을 수행합니다. 도트 곱, 행렬 곱셈, 제곱근 계산, 벡터 변환 등 다양한 연산에 걸쳐 있습니다. 이러한 연산의 성능 차이는 전체 프레임 타임에 상당한 영향을 미칠 수 있습니다. 다음 그래프는 일반적인 수학 연산 일곱 개의 상대적 비용을 보여줍니다. 측정은 단일 128비트 SSE 레지스터에 저장된 네 개의 32비트 부동 소수점 값으로 구성된 벡터를 사용합니다. 행렬은 네 개의 벡터로 구성되며, 네 개의 128비트 SSE 레지스터에 걸쳐 총 16개의 32비트 부동 소수점 값을 가집니다. 이 연산들은 한 프레임 내에서 흔히 사용되기 때문에 선택되었습니다.
  • 도트 곱: 벡터의 도트 곱을 계산합니다.
  • 벡터 변환: 벡터를 행렬과 곱해 변환합니다.
  • 벡터 덧셈: 두 벡터를 더합니다.
  • 크로스 곱: 두 벡터의 크로스 곱을 계산합니다.
  • 벡터 사인: 벡터의 각 요소에 대한 사인 값의 근사치를 계산합니다.
  • 벡터 FMA (fused multiply-add): 두 벡터를 곱하고 세 번째 벡터를 더합니다.
  • 역제곱근: 벡터 길이의 역제곱근을 계산합니다.
Figure 1: 연산 수행 시간. 낮을수록 좋음. 이 그래프에서 y축은 연산 수행 시간입니다. 각 막대는 모든 IHV에 걸쳐 측정된 해당 연산의 시간 범위를 나타냅니다. 성능 코어 또는 효율 코어에서 테스트를 실행했을 때의 최소 및 최대 시간입니다. 예상대로 코어 유형 간 성능은 다릅니다. 그러나 이 수치들의 핵심 시사점은 성능이 모든 연산과 IHV에 걸쳐 균일하게 스케일링되지 않는다는 점입니다. IHV 간 결과의 변동성은 대체로 유사하지만 일부는 변동성이 더 컸습니다. 변동성이 더 큰 연산은 Vector Sin과 Inverse Square Root 연산에서 가장 두드러졌습니다. 이 차이는 성능 코어와 효율 코어 간의 아키텍처 선택에서 비롯됩니다. 이 격차의 정확한 원인은 여러 소스에서 올 수 있습니다. 예를 들어 성능이 좋은 코어는 더 많은 부동 소수점 파이프를 가져 병렬 연산이 더 많을 수 있습니다. 정확한 이유는 각 IHV와 CPU 세대에 고유합니다. 특정 연산에서는 성능 격차가 존재하지 않는 구성이 있을 수도 있습니다. 결국 타이틀은 IHV 전반에 걸친 이 변동성을 고려해야 합니다.

실제 세계

앞서 소개된 수치는 특정 벤치마크를 개별적으로 보여줄 뿐이며 이야기의 일부만 전달합니다. 다음 수치는 두 개의 메인 스레드(Simulation과 Render)와 잡 스레드 집합을 사용하는 AAA 게임 엔진의 벤치마크에서 나온 것입니다. 벤치마크는 세 가지 테스트 구성을 사용합니다.
  • 두 메인 스레드가 성능 코어에서만 실행됩니다.
  • 두 메인 스레드가 효율 코어에서만 실행됩니다.
  • 두 메인 스레드가 시스템의 모든 코어에서 실행됩니다.
각 테스트 구성에서 잡 스레드는 시스템의 모든 코어에 걸쳐 자유롭게 이동할 수 있습니다. 렌더링 작업은 가능한 가장 낮은 해상도와 품질을 사용합니다. 이 설정은 전체 테스트 실행 동안 타이틀이 CPU 바운드임을 보장합니다. 또한 이 설정은 Simulation 스레드를 프레임 레이트의 주도적 요인으로 만듭니다. 이 그래프는 한 프레임 동안 각 스레드에서 소요된 시간을 보여줍니다. 평균 프레임 타임과 1% high를 모두 표시합니다. 1% high는 가장 긴 1%의 프레임에 대한 임계값입니다. 모든 프레임 중 오직 1%만이 이 값을 넘습니다. 이 수치는 스터터링 프레임 수에 대한 감을 잘 줍니다. 평균에서 멀어질수록 타이틀의 스터터링이 심합니다. 모든 경우에서 값이 낮을수록 긴 프레임이 적고 스터터링이 덜 눈에 띕니다. 앞서 언급한 대로, 표시된 데이터는 IHV 전반의 종합입니다. 각 IHV는 자기 토폴로지에 대한 선택에 따라 각 테스트에 대해 다른 스케일링을 가집니다. 정확한 수치는 다르지만 패턴은 IHV 전반에서 유사합니다. 각 CPU 세대는 다른 절충안을 만들어 상대적 수치를 조정할 수 있습니다. 기존의 모든 CPU IHV와 세대에 대한 수치를 만드는 것은 이 문서의 범위를 벗어납니다. Figure 2: 평균 소요 시간과 1% high. 낮을수록 좋음. 이 그래프에서 y축은 한 프레임에 소요된 시간입니다. 각 막대는 모든 IHV에 걸쳐 측정된 해당 연산의 시간 범위를 나타냅니다. 테스트된 각 스레드 토폴로지에 대한 최소 및 최대 시간입니다. OS 스레드 스케줄러는 스레드가 수행하는 작업량을 판단하기 위해 휴리스틱을 사용합니다. 이러한 휴리스틱은 OS의 선택과 IHV의 드라이버 튜닝의 조합입니다. 스케줄러는 이 정보를 사용해 특정 스레드가 어느 코어에서 실행되어야 할지 결정합니다. 스레드가 많은 작업을 하고 있다고 판단하면 스케줄러는 그 스레드를 더 성능이 좋은 코어로 이동시킵니다. 스케줄러 휴리스틱 때문에, 크리티컬 스레드가 자유롭게 이동하도록 허용될 때 타이틀이 더 빠르게 실행되는 직관에 반하는 동작이 발생할 수 있습니다. 이 동작이 항상 나타나는 것은 아니며 워크로드, 코어 토폴로지, 그리고 정확한 스케줄러 휴리스틱에 따라 달라집니다. 여기서 테스트한 경우에서는 Simulation 스레드가 프레임당 Render 스레드보다 실행에 더 많은 시간을 씁니다. 스케줄러는 이 스레드를 더 성능이 좋은 코어에 배치하는 것을 선호합니다. 또한 Render 스레드는 작업을 기다리며 일시 정지되는 시간이 더 많으므로 성능이 덜한 코어에 배치하기로 결정합니다. 순 효과는 이 결정이 잡 스레드 중 하나를 위해 더 성능 좋은 코어를 열어준다는 것입니다. Render 스레드가 더 많은 작업을 수행했다면 이런 상황이 발생하지 않을 수도 있습니다. 그러나 이 그래프에서 가장 흥미로운 시사점은 1% high 수치에 있습니다. 크리티컬 스레드가 자유롭게 이동할 때 평균이 같더라도, 크리티컬 스레드를 성능 코어에 고정했을 때보다 1% high가 더 나쁩니다. 이는 크리티컬 스레드가 자유롭게 이동할 때 프레임 레이트 스터터가 더 심하다는 뜻입니다. 가장 흔한 이유는 스케줄러 휴리스틱이 슬라이딩 윈도우 위에서 동작한다는 점입니다. 프레임에 필요한 작업량의 급증에 반응하는 데 시간이 걸립니다. 그동안 크리티컬 스레드가 덜 성능적인 코어에서 실행되고 있을 수 있습니다.
각 제조사는 자기 하드웨어에 맞게 이러한 스케줄러 휴리스틱을 조정할 수 있습니다. 예를 들어 AMD에는 Workload Profile Scheduling이 있고, Intel에는 Thread Director 기술이 있습니다. 각 IHV는 또한 SoC의 특정 토폴로지에 맞게 휴리스틱을 조정하는 경향이 있습니다.

권장 사항

스레드 어피니티

효율 클래스가 하나뿐인 데스크톱 머신에서 실행할 때의 스레드 어피니티 조언은, 하드 스레드 어피니티를 설정하지 말고 스레드들이 모든 코어에 걸쳐 자유롭게 이동하도록 두라는 것입니다. 이 불확실성의 주된 이유는 사용자의 머신에서 다른 어떤 프로세스가 실행 중일지 미리 알 수 없기 때문입니다. 하드 어피니티를 가진 스레드가 시스템 내 다른 고우선순위 스레드와 같은 코어를 공유하게 될 수 있습니다. 순 효과는 타이틀 스레드가 실행을 위한 자원을 굶주리게 되는 것입니다. 이 경합이 프레임 크리티컬 스레드에서 발생하면 타이틀은 일관되지 않은 프레임 타임을 경험할 수 있습니다. OS가 타이틀 스레드를 계속 실행하도록 이동시킬 곳이 확보되어 있어야 합니다. 그러나 이 조언은 이기종 CPU에서 실행할 때는 바뀌어야 합니다. 주된 이유는 OS가 타이틀의 어떤 스레드나 잡이 프레임 크리티컬인지 반드시 알지 못하기 때문입니다. 어떤 스레드가 프레임 크리티컬이라고 판단하는 휴리스틱이 있지만, 이 휴리스틱은 여전히 잘못된 선택을 할 수 있습니다. 이러한 스레드나 잡 중 하나를 성능이 낮은 코어로 이동시켜 타이틀에 성능 문제가 발생할 가능성을 높일 수 있습니다. 권장 사항은 타이틀 스레드가 최소한 3~4개의 코어에서 실행될 수 있도록 하고, 더 많으면 좋다는 것입니다. 예를 들어 성능 코어 1개와 저성능 코어 3개가 있는 저사양 시스템에서는 스레드가 모든 코어에 걸쳐 자유롭게 이동하도록 허용해야 합니다. 성능 코어가 3개 이상인 머신에서는 프레임 크리티컬 스레드를 그 성능 코어들로만 제한할 수 있습니다. SMT를 지원하는 다코어 고성능 머신에서는 일부 타이틀이 자신의 스레드를 물리 코어에 잠그려 시도할 수 있습니다. 이 기법은 여러 고우선순위 스레드가 하나의 물리 코어의 자원을 공유할 때 물리 코어 수준의 경합을 피하기 위한 것입니다. 그러나 일반적으로 이 경우를 고려하는 것은 권장되지 않습니다. OS는 이미 시스템 전반에 이 동작을 적용합니다. 스레드를 먼저 물리 코어에 스케줄링하고 필요할 때만 더 많은 논리 코어를 사용합니다. 타이틀이 고려해야 하는 유일한 경우는 시스템에 타이틀의 크리티컬 스레드를 지원할 수 있을 만큼 성능 코어가 충분한 경우입니다. 이 경우에는 프레임 크리티컬 스레드를 성능 좋은 코어에 잠그는 것이 바람직할 수 있습니다. 그러나 그 스레드들은 모든 성능 좋은 코어에 걸쳐 자유롭게 이동하도록 허용해야 합니다. 일부 경우에 모든 스레드가 모든 코어에 걸쳐 이동하도록 허용하면 성능이 향상될 수 있지만, 이 동작은 모든 CPU 제조사에서 일관적이지 않습니다. 타이틀이 모든 스레드에 걸쳐 워크로드를 완전히 균형 있게 배분할 수 있는 작업 훔치기(work stealing) 시스템을 구현했다면, 어떤 잡 스레드든 모든 코어에 걸쳐 자유롭게 이동하도록 허용해야 합니다. 고우선순위 또는 저우선순위 잡 스레드인지는 상관없습니다. 작업 훔치기 알고리즘이 자동으로 부하를 균형 있게 배분하고 가장 많은 작업을 가장 짧은 시간에 완료할 수 있게 합니다. 성능이 좋은 코어의 스레드는 프레임 동안 더 많은 잡을 실행합니다. 여러 개의 프레임 크리티컬 스레드가 있다면 어떤 두 프레임 크리티컬 스레드도 같은 코어에서 실행될 수 없도록 어피니티를 설정하는 것이 유용할 수 있습니다. 예를 들어 두 개의 프레임 크리티컬 스레드가 있다면 서로의 XOR로 어피니티를 설정해 OS가 두 스레드를 서로 경합하게 할 가능성을 제거합니다. 다시 말하지만, 각 프레임 크리티컬 스레드가 최소 3~4개의 코어에 걸쳐 이동할 수 있도록 하면서 이 케이스를 지원할 만큼 코어가 충분한 경우에만 이 옵션을 고려하세요.

스레드 토폴로지

이기종 환경에서 스레드에 작업을 할당하는 방법을 고려할 때, 주요 데이터는 다양한 태스크의 길이와 그들이 서로 어떻게 상호작용하는지에 관한 것입니다. 각 태스크의 평균 길이, 태스크 간 의존성, 각 태스크의 우선순위 등입니다. 잡 스레드가 크리티컬 스레드나 잡의 시간 급증을 얼마나 흡수할 수 있나요? 그 스레드나 잡 중 하나가 35퍼센트 더 느리게 실행된다면 프레임 타임에 얼마나 영향을 미치나요? 예를 들어 태스크가 길게 실행된다면 이 지연이 프레임의 나머지에 얼마나 영향을 미치나요, 시스템은 그 태스크가 완료될 때까지 멈추나요? 크리티컬 스레드나 잡에 추가 오버헤드를 넣어 이기종 시스템이 프레임 타임에 얼마나 영향을 미치는지 파악하는 것이 유용할 수 있습니다. 테스트로서 무작위로 잡이나 작업 블록이 25%–35% 더 시간이 걸리도록 만들어 보세요. 전체 프레임 타임의 변화 정도는 잡 시스템이 실행 급증을 얼마나 잘 흡수할 수 있는지를 보여줍니다. 이상적인 상황은 프레임 타임에 거의 변화가 없는 것입니다. 이는 시스템이 프레임 타임의 임의 급증을 흡수할 수 있음을 의미합니다. 급증은 성능이 낮은 코어에서 실행되거나 시스템 내 다른 임의 프로세스가 CPU 시간을 사용하는 데서 발생합니다.

스레드 통신

잠금 프리미티브를 사용하는 스레드 통신에는 신중해야 합니다. 컨텍스트 스위치의 오버헤드는 새 스레드가 실행되기 전 수신자 코어의 C-State와 밀접하게 결합되어 있습니다. 새 스레드가 실행되려면 코어가 C0, 즉 실행 상태여야 합니다. 가장 저렴한 경우는 코어가 이미 실행 중이었을 때로, 이미 C0에 있습니다. 그러나 코어가 유휴 상태라면 더 낮은 C-State에 있습니다. C-State 수준은 더 깊은 수면 수준에 해당합니다. 더 깊은 수면은 코어가 사용하는 전력이 더 적음을 의미합니다. 그러나 더 깊은 수면에서 깨어나 다시 실행을 시작하는 것은 비용이 더 큽니다. 가장 흔한 저전력 C-state는 C1으로 27 μs의 오버헤드를 추가할 수 있습니다. 다음 C-state인 C2는 약 40100 μs의 오버헤드를 추가할 수 있습니다. 정확한 수치는 IHV와 SoC에 따라 다릅니다. 코어가 유휴 시 어떤 C-State에 있는지 제어하는 여러 변수가 있으며, 이는 반응성과 전력 사용 사이의 균형입니다. 더 낮은 C-State는 더 높은 C-State보다 적은 전력을 사용합니다. 코어가 먼저 C1에 들어간 다음 한동안 코드를 실행하지 않으면 C2로 전환하는 것이 흔합니다. 또 다른 우려가 있습니다. 잡을 제출하는 스레드와 잡을 실행하는 스레드가 성능 프로필이 다른 코어에 있을 때 무슨 일이 벌어지느냐입니다. 기술적으로 잡 스레드가 새 잡이 생성되는 것보다 더 빨리 잡을 완료하는 것이 가능합니다. 이 상황은 잡을 실행하는 스레드가 잡을 제출하는 스레드보다 더 빠른 코어에 있을 때 발생할 가능성이 더 큽니다. 잡이 생성되는 것보다 실행에 시간이 덜 걸리면, 타이틀은 잡 스레드가 새 잡을 기다리며 계속해서 일시 정지되는 상황에 빠질 수 있습니다. 순 효과는 잡 스레드가 계속 일시 정지되기 때문에 잡 실행 비용이 더 커지는 것입니다. 컨텍스트 스위치의 오버헤드 비용이 각 잡 실행 시간에 더해집니다. 이 오버헤드는 역설적으로 성능이 좋은 코어에서 실행할 때 프레임 레이트가 더 낮아지는 결과를 초래할 수 있습니다. 예를 들어 이전에 250 μs가 걸리던 작업이 갑자기 275 μs가 걸리기 시작하면 이는 10%의 성능 저하입니다. 이 경우를 감안해 권장 사항은 잡을 생성되는 대로가 아니라 배치로 제출하는 것입니다. 예를 들어 시스템이 잡을 만들고, 제출하고, 만들고, 제출하는 패턴을 따른다면 이 패턴을 조정해 대신 배치로 먼저 만든 뒤 한꺼번에 모두 제출하도록 하세요. 타이틀이 WaitForSingleObject를 필요로 하는 객체를 사용한다면 작은 스핀 루프 안에 배치해야 합니다. 함수를 timeout 0으로 호출하고, 스핀이 완료된 후에는 타이틀의 일반 timeout 값을 사용하세요. 그러나 이 문제에 대한 가장 좋은 해결책은 Slim Reader/Writer(SRW) 잠금, Critical Section, WaitOnAddress 같은 최신 동기화 API를 사용하는 것입니다. 이 동기화 API들은 내부적으로 어떤 형태의 스핀을 포함해 스레드가 지속적으로 일시 정지되는 것을 방지할 수 있습니다. 이 스핀은 짧은 시간 동안 유지되는 잠금에 특히 유용합니다. 긴 스핀 락은 피해야 합니다. 예를 들어 스레드가 수행할 작업을 기다리며 계속 스핀하는 패턴은 피하세요. 코어에서 수행해야 할 다른 작업이 있다면 스케줄러는 결국 스피닝 스레드를 일시 정지해 이 작업을 수행합니다. 이러한 프리엠션이 발생하면 일시 정지된 스레드가 상당한 시간 동안 정지된 상태로 남을 가능성이 큽니다. 이 동작은 다른 스레드의 기아 상태를 방지하기 위한 것입니다. 타이틀은 언제 일시 정지가 일어날지 거의 통제할 수 없기 때문에 프레임 레이트 스터터로 이어질 수 있습니다.

잡 시스템

최상의 성능을 위해 엔진을 전용, 장기 실행 프레임 크리티컬 스레드에 의존하기보다는 잡을 중심으로 구성하세요. 예를 들어 Simulation/Render 스레드 모델은 두 개의 영속적인 프레임 크리티컬 스레드를 만듭니다. 그러나 긴 잡은 어떤 잡 스레드든 프레임 크리티컬 스레드처럼 동작하게 만들 수 있습니다. 잡이 10 ms가 걸린다면 원래 역할과 무관하게 그 기간 동안 그 잡을 실행하는 스레드는 프레임 크리티컬이 됩니다.

작업 훔치기

가장 중요한 권장 사항은 전체 잡 시스템에서 작업 훔치기를 사용하고, 어떤 프레임 크리티컬 스레드도 작업 훔치기 알고리즘에 참여하도록 하는 것입니다. 예를 들어 렌더 스레드가 렌더 관련 잡의 완료를 기다리고 있다면 그 잡들 중 일부를 실행해야 합니다. 이 동작은 이미 많은 게임 엔진에서 흔한 관행입니다. 그러나 이 종류의 동작이 이기종 코어와 가능한 OS 프리엠션으로 인한 프레임 급증을 게임 엔진이 흡수하는 데 매우 중요하기 때문에 여기서 언급할 가치가 있습니다. 잡이 실행에 더 오래 걸리면 프레임 타임 급증의 위험이 증가합니다. 이 동작은 잡이 성능이 낮은 코어에서 실행되거나 PC 내 다른 작업에 의해 발생할 수 있습니다. 권장 사항은 엔진이 잡이 프레임의 약 2%를 차지하도록 목표를 잡는 것입니다. 이 예산은 예를 들어 60 fps로 실행되는 게임의 경우 약 333 μs 이하를 의미합니다. 필요한 경우 여러 개의 작은 잡을 큰 잡으로 배치화해 2% 수치를 목표로 해야 합니다. 이 접근은 잡 크기, 스레드 통신, 그리고 급증 흡수 능력 사이에 좋은 절충안을 제공합니다. 이 예시는 긴 프레임 크리티컬 잡이 시스템 성능에 미치는 영향을 보여줍니다. 이 예시에서 각 잡은 동일한 수의 연산으로 구성됩니다. 이 예시에서 하나의 잡이 느린 코어에서 375 μs, 빠른 코어에서 250 μs가 걸립니다. 롱 폴 잡은 다른 잡들보다 6배 더 비쌉니다. 최악의 상황은 롱 폴 잡이 느린 코어 중 하나에 배치되는 경우입니다. 이는 프레임 타임에 극적인 영향을 미칠 수 있습니다. 이 시나리오에서 작업 실행에 걸리는 총 시간은 2,250 μs, 즉 375 μs의 6배입니다. Figure 3: 느린 코어에서 실행되는 롱 폴 예시. 롱 폴 잡이 빠른 코어에 배치되면 상황이 개선됩니다. 이 경우 총 시간은 1,500 μs입니다. 여전히 좋지는 않지만 더 낫습니다. Figure 4: 빠른 코어에서 실행되는 롱 폴 예시. 최선의 결과는 잡 크기를 작고 균등하게 분산해 롱 폴 잡을 피하는 것입니다. 이 예시에서 총 실행 시간은 1,125 μs로 떨어지며, 이는 같은 양의 작업을 완료하면서 이전 예시의 절반 시간입니다. Figure 5: 적절한 잡 길이를 사용해 롱 폴 잡을 피함. 더 중요한 것은, OS가 잡 실행 중간에 인터럽트하려 할 때 시스템이 워크로드를 자동으로 균형 있게 재조정한다는 점입니다. 이 경우 OS가 빠른 코어에 375-μs 태스크로 타이틀을 인터럽트했습니다. 이 재조정은 전체 프레임 타임에 영향을 미치지 않으며 1,125 μs로 유지됩니다. 잡 중 하나가 느린 코어의 여유 시간으로 마이그레이션되었습니다. Figure 6: 워크로드 재조정을 통한 OS 인터럽션이 안정적인 프레임 타임 유지. 이 예시는 롱 폴 잡이 즉시 시작할 수 있는 경우만 다룹니다. 잡 큐에서 나중까지 시작할 수 없다면 상황은 더 나빠집니다. 결국은 암달의 법칙으로 귀결됩니다. 어떤 시스템이든 실행 시간은 그 시스템에서 가장 오래 실행되는 부분에 묶여 있습니다. 그러한 롱 폴 스레드의 실행 시간을 증가시키는 급증은 엔진이 흡수하기가 불가능합니다. 이러한 급증은 느린 코어에서 실행되거나 시스템 내 다른 고우선순위 프로세스의 인터럽션에서 올 수 있습니다.

마무리

이러한 권장 사항을 구현함으로써 게임이 다른 CPU 아키텍처와 다른 Windows 프로세스로부터의 간섭에서도 안정적으로 성능을 발휘하고 견고함을 유지하도록 할 수 있습니다.

부록

코어의 효율 클래스 판별

GetLogicalProcessorInformationEx 함수를 사용해 시스템의 각 프로세서의 효율 클래스를 판별하세요. 함수가 반환한 데이터를 순회하며 효율 클래스와 코어 마스크가 포함된 RelationProcessorCore 블록을 찾습니다.

전력 사용 기반 스레드 고정

앞서 언급한 대로, 데스크톱 환경에서 어피니티를 통한 코어 고정은 머신의 다른 프로세스로부터의 간섭 가능성 때문에 권장되지 않습니다. 그러나 Windows는 타이틀이 어디서 작업을 실행해야 할지 OS에 힌트를 제공할 수 있는 방법을 제공합니다. 이러한 안내의 한 가지 방법은 전력 관리 API를 통하는 것입니다.

CPU Sets를 사용해 효율 클래스 조회

CPU Sets 사용은 시스템 내 각 코어의 효율 클래스를 조회하는 또 다른 방법입니다.

CPU Sets를 사용해 스레드를 효율 클래스에 할당

CPU Sets는 스레드의 어피니티 마스크를 명시적으로 설정하는 대신 사용할 수도 있습니다. 명시적 어피니티 마스크 대신 CPU Sets를 사용하는 것이 권장됩니다.
마지막 수정일 2026년 8월 13일