Devin.KR

CS · 기본

개발자를 위한 컴퓨터 사이언스 - 내 코드가 실제로 도는 곳

CPU 명령 처리 - 클록과 파이프라인·분기 예측·물리 코어와 하드웨어 스레드 (CS 기초 2장)

클록과 명령 처리량의 차이, 파이프라인의 겹침과 분기 예측, 물리 코어와 논리 CPU의 공유 관계를 작은 모형으로 관찰하고 CPU 사용률과 병렬화 한계의 해석 기준을 익힌다.

개발자 · 원고 갱신

이 장에서 배우는 것

1장에서 값의 비트 표현과 인코딩 경계를 살폈다. 이번 장은 그 값을 처리하는 CPU의 흐름을 연결한다. 선수지식은 반복문과 간단한 산술이며 실제 기계어를 읽을 필요는 없다.

  • 클록 수와 명령 처리량이 단순히 비례하지 않는 이유를 설명한다.
  • 의존성과 분기 예측이 겹쳐 실행하는 흐름에 주는 영향을 구분한다.
  • 물리 코어와 논리 CPU의 자원 공유 관계를 설명한다.
  • CPU 100%와 작업자 수만으로 잘못된 증설·튜닝 결론을 내리지 않는다.

개념

서버를 더 높은 클록의 CPU로 바꿨는데 요청 처리량이 기대만큼 늘지 않는다. 코어 수에 맞춰 작업자를 늘리면 오히려 응답이 흔들리기도 한다. 이런 상황에서 GHz와 코어 수만 비교하면 서로 다른 제한을 같은 문제로 묶게 된다. 먼저 명령이 준비되고 실행되어 결과로 확정되는 과정에서 무엇을 기다리는지 구분해야 한다.

클록은 작업 완료 횟수가 아니다

클록은 내부 동작을 조율하는 시간 기준이고, 명령은 프로그램이 CPU에 요청하는 작업 단위다. 하나의 명령이 여러 내부 작업으로 나뉠 수 있고 여러 명령의 작업이 겹쳐 진행될 수 있다. 한 클록에 반드시 명령 하나가 끝난다는 관계는 성립하지 않는다. 클록이 높아져도 데이터 도착이나 앞선 결과를 기다리는 시간이 지배하면 전체 요청은 비례해서 빨라지지 않는다.

명령당 필요한 시간과 단위 시간에 끝나는 명령 수 역시 다른 관점이다. 긴 생산 라인에서 제품 하나가 끝까지 이동하는 시간은 길어도 서로 다른 제품이 여러 구간에 있으면 완료 간격은 짧아질 수 있다. 이 비유는 파이프라인의 겹침만 설명하며 실제 CPU의 단계 수를 뜻하지 않는다. 명령의 의존성과 메모리 접근을 빼고 계산한 처리량은 상한 모형에 가깝다.

관찰값직접 말해 주지 않는 것
클록 빈도시간당 주기 수요청당 완료 작업량
명령 처리량시간당 완료한 명령명령이 수행한 업무의 유용성
CPU 사용률관찰 구간의 CPU 시간 비중대기 원인과 추가 처리 여력

겹쳐 실행해도 의존성은 남는다

설명을 위해 명령 가져오기, 해석, 실행, 결과 확정의 네 단계로 나눈다. 앞 명령이 실행 단계에 있을 때 뒤 명령의 해석을 준비하면 작업을 겹칠 수 있다. 하지만 뒤 명령의 입력이 앞 명령의 출력이면 결과가 준비되기 전까지 그 의존성을 무시할 수 없다. 실제 CPU는 다양한 내부 기법으로 빈 구간을 활용하지만 프로그램이 요구하는 결과의 일관성은 지켜야 한다.

분기에서는 어느 경로의 명령을 먼저 준비할지 결정해야 한다. 예측한 경로가 맞으면 준비한 작업이 이어지고, 틀리면 잘못 준비한 일부 작업을 버리고 올바른 경로를 다시 가져온다. 예측 실패의 비용은 CPU와 주변 명령에 따라 달라지므로 고정된 클록 수로 외우지 않는다. 파이썬 if문의 실행 시간만으로 CPU 분기 예측 실패 횟수를 알아낼 수도 없다.

실습의 전환 횟수는 입력 패턴을 관찰하는 값이다. 실제 예측기는 직전 분기의 참·거짓만 기억하는 단순한 장치가 아니며, 같은 전환 횟수라도 예측 결과가 달라질 수 있다. 따라서 모형에서는 일부러 직전 결과만 예측하는 규칙을 선언한다. 모형에서 센 실패와 하드웨어 측정값을 구분한다.

물리 코어와 논리 CPU를 구분한다

물리 코어는 명령을 처리하는 실행 자원을 가진 하드웨어 단위다. SMT를 제공하는 코어는 여러 하드웨어 스레드의 상태를 유지해 운영체제에 여러 논리 CPU로 보일 수 있다. 이 논리 CPU들은 실행 자원의 일부를 공유하므로 물리 코어가 그 수만큼 복제된 것은 아니다. 또한 모든 CPU가 SMT를 제공하는 것도 아니고 코어마다 성능 특성이 같다는 보장도 없다.

작업자를 늘려 얻는 이익은 비어 있던 자원을 얼마나 채우는지에 달려 있다. 한 작업이 이미 연산 자원이나 메모리 대역폭을 충분히 사용하면 작업자를 더해도 서로 경쟁할 수 있다. 독립 작업이어도 공통 자료를 잠그거나 결과를 한곳에 모으는 단계는 병렬화 효과를 제한한다. 프로세스와 스레드의 주소 공간 차이는 5장에서 다루며, 여기서는 보이는 CPU 수를 성능 배수로 읽지 않는 데 집중한다.

가상 구성 예시운영체제에 보이는 수공유 관계
2코어·코어당 1스레드논리 CPU 2개코어별 실행 자원
2코어·코어당 2스레드논리 CPU 4개같은 코어의 일부 자원 공유
CPU 할당이 제한된 환경조회 도구마다 다를 수 있음호스트 전체와 허용 자원 구분

명령은 가져오기·해석·실행·확정의 개념 흐름을 따른다. 같은 코어의 하드웨어 스레드는 일부 실행 자원을 공유한다.

명령은 가져오기·해석·실행·확정의 개념 흐름을 따른다. 같은 코어의 하드웨어 스레드는 일부 실행 자원을 공유한다.

직접 확인하기

실습은 Python 3 표준 라이브러리만 사용한다. 각 코드 블록을 별도 파일로 저장해 python3 파일명.py로 실행할 수 있다. 블록 사이에 공유하는 변수나 파일은 없다.

겹침과 예측을 작은 모형으로 본다

instructions, stages = 8, 4
serial = instructions * stages
ideal_pipeline = instructions + stages - 1
print("겹치지 않음", serial)
print("이상적 겹침", ideal_pipeline)
print("명령 하나의 단계 수", stages)

실행 결과다. 시간 측정 비율과 환경 의존 값은 실행할 때 달라질 수 있다.

결과
----
겹치지 않음 32
이상적 겹침 11
명령 하나의 단계 수 4

32와 11은 네 단계가 각각 한 모형 시간 단위를 쓰고 중단이 전혀 없다는 가정에서 나온다. 첫 명령이 끝나는 데는 여전히 네 단계가 필요하며, 이후에 겹침의 이익이 나타난다. 이를 특정 CPU의 실행 시간이나 실제 명령 수로 사용하면 안 된다. 의존성과 분기 실패가 추가되면 이상적인 겹침에서 멀어지는 이유를 설명하는 용도다.

patterns = {"연속": [False] * 8 + [True] * 8,
            "교대": [False, True] * 8}
for name, outcomes in patterns.items():
    guess, misses = False, 0
    for actual in outcomes:
        misses += guess != actual
        guess = actual
    print(name, "모형 실패", misses)

실행 결과다. 시간 측정 비율과 환경 의존 값은 실행할 때 달라질 수 있다.

결과
----
연속 모형 실패 1
교대 모형 실패 15

이 모형에서는 연속 패턴이 한 번, 교대 패턴이 열다섯 번 틀린다. 비교한 것은 예측 규칙과 입력 패턴의 조합이며 실물 CPU는 교대 패턴을 더 잘 예측할 수도 있다. 입력을 정렬해 빨라졌다는 관찰 하나로 분기 예측이 유일한 원인이라고 결론 내리지 않는다. 정렬 비용과 메모리 접근 변화까지 합쳐 업무 전체의 시간을 비교해야 한다.

병렬화 상한과 시간 분모를 확인한다

serial_fraction = 0.25
for workers in [1, 2, 4, 8]:
    relative_time = serial_fraction + (1 - serial_fraction) / workers
    print(workers, round(1 / relative_time, 3))

실행 결과다. 시간 측정 비율과 환경 의존 값은 실행할 때 달라질 수 있다.

결과
----
1 1.0
2 1.6
4 2.286
8 2.909

전체 작업의 25%가 직렬이고 나머지만 이상적으로 나뉜다는 계산이다. 작업자가 여덟이면 속도 향상은 약 2.909배이고 여덟 배가 아니다. 실제로는 작업 분배와 결과 결합 비용도 있으므로 이 숫자는 보장 성능이 아니다. 요청 전체에서 직렬 구간을 찾지 않고 작업자 수부터 늘리는 접근의 한계를 보여 준다.

import os
import time
start_wall = time.perf_counter()
start_cpu = time.process_time()
time.sleep(0.05)
wall = time.perf_counter() - start_wall
cpu = time.process_time() - start_cpu
print("조회된 논리 CPU", os.cpu_count())
print("CPU 시간 / 경과 시간", round(cpu / wall, 3))

실행 결과다. 시간 측정 비율과 환경 의존 값은 실행할 때 달라질 수 있다.

결과
----
조회된 논리 CPU 10
CPU 시간 / 경과 시간 0.0

짧은 대기 동안 벽시계 시간은 흐르지만 이 프로세스가 CPU를 사용한 시간은 작다. 비율이 0으로 반올림될 수도 있으며 정확히 0일 것을 기대하는 테스트는 적절하지 않다. os.cpu_count는 시스템 논리 CPU 수를 알려 주는 관찰값으로, 현재 작업에 배정된 CPU 시간 한도를 직접 뜻하지 않는다. 이 예제는 대기와 연산을 구분할 뿐 실제 서비스의 CPU 병목을 판정하지 않는다.

환경별 차이

환경관찰 출발점해석 경계
LinuxCPU 토폴로지와 프로세스별 시간논리 CPU와 물리 코어를 구분
macOS코어 구성과 프로세스별 시간코어 종류와 도구의 집계 기준 확인
컨테이너허용 CPU 집합과 CPU 시간 제한호스트 논리 CPU 수를 할당량으로 해석하지 않음

모니터링 도구는 한 논리 CPU를 100%로 표시하거나 전체 할당량을 100%로 정규화할 수 있다. 같은 프로세스가 여러 CPU에서 실행되면 전자의 값은 100%를 넘을 수 있다. 대시보드 제목만 보고 비교하지 말고 측정 구간, 분자, 분모를 확인해야 한다. 사용률이 높다는 관찰과 요청 지연이 CPU 때문이라는 진단 사이에는 실제 실행 경로의 근거가 필요하다.

가상 머신 안에서는 보이는 CPU 토폴로지가 물리 장비와 그대로 일치하지 않을 수 있다. 절전 정책이나 다른 작업의 영향도 측정값을 바꾸므로 환경을 고정하고 반복한다. 이 장은 특정 CPU의 분기 실패 지연이나 실행 포트 개수를 수치로 제시하지 않는다. 모델별 세부 정보가 필요한 최적화는 해당 제조사 문서와 하드웨어 계측을 함께 사용해야 한다.

실무에서 자주 틀리는 것

클록이 두 배면 응답도 두 배 빨라진다

증상은 CPU를 바꿔도 지연이 거의 줄지 않는 것이다. 흔한 오진은 새 CPU가 제 성능을 못 낸다는 판단이다. 실제 원인은 데이터 대기나 직렬 구간이 지배하는 것일 수 있다. CPU 시간과 경과 시간을 나누고 요청의 대기 구간을 확인한다. 서로 다른 CPU에서는 명령당 처리 특성도 달라 GHz만 맞춰 비교할 수 없다.

논리 CPU 수만큼 작업자를 늘린다

증상은 작업자가 많아지면서 처리량은 정체되고 꼬리 지연만 커지는 것이다. 흔한 오진은 스레드 수가 아직 부족하다는 판단이다. 실제 원인은 실행 자원 공유, 직렬 구간 또는 공통 메모리 경합일 수 있다. 동시 작업 수를 단계적으로 바꾸며 처리량과 지연을 함께 기록한다. 작업자 수는 처리량과 지연을 같이 보고 결정한다.

CPU 100%면 모든 코어가 가득 찼다

증상은 100% 그래프를 보고 즉시 서버 증설을 결정하는 것이다. 흔한 오진은 이 수치가 장비 전체 사용량이라는 판단이다. 실제 원인은 단일 논리 CPU 기준 표시일 수 있다. 도구의 분모와 컨테이너 할당 기준을 먼저 확인하고 실행 큐와 요청 시간을 함께 본다. 사용률은 수행한 업무의 효율을 직접 보여 주지 않는다.

스스로 확인하기

문제와 해설

  1. 네 단계에 명령 여덟 개를 겹쳐 처리하는 모형의 총 시간이 11이다. 명령 한 개의 지연도 1로 줄었다고 말할 수 있는지 판단하라.
  2. 직렬 비율 25%인 모형에서 작업자를 끝없이 늘리면 향상 배수는 어디에 가까워지는지 계산하라.
  3. 논리 CPU 4개가 보이는 환경에서 물리 코어도 4개라고 결론 내릴 수 있는지 설명하라.

해설 1 말할 수 없다. 첫 명령은 네 단계를 지나야 하며, 줄어든 것은 여러 명령을 완료하는 총 시간이다.

해설 2 나머지 구간의 시간이 작아져도 전체의 25%는 남는다. 추가 비용이 없다는 가정에서 4배에 가까워진다.

해설 3 SMT와 가상화 때문에 같다고 단정할 수 없다. 코어 공유 관계와 실행 환경의 토폴로지를 따로 확인해야 한다.

참고 자료: Intel 최적화 문서 모음 · Intel 멀티스레드 개발 가이드 · Python 운영체제 인터페이스. 공식 자료는 사실 확인에만 사용했다. 설명·예제·문제·도식은 이 원고를 위해 새로 작성했다.

다음 장은 CPU가 값을 기다리는 경로를 메모리 계층과 캐시로 확장한다. 같은 연산 횟수라도 접근 순서가 성능을 바꾸는 이유를 살핀다.

READER FEEDBACK

질문·오탈자·의견

내용에 관한 질문이나 오탈자, 더 나은 설명을 위한 의견을 남겨 주세요. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.