← 문제로

9. 질문 — 멀티코어 확장성: 틱 루프 동시성 설계 (종합)

난이도 최상
내 답안
모범답안

모범답안 — 멀티코어 확장성과 틱 루프 동시성 설계 (종합)

난이도: 최상

핵심 답변

코어 대비 성능이 안 오르는 건 대개 공유 상태 경합 때문이다: 락 경합(직렬화), false sharing(같은 캐시라인 핑퐁), 캐시 일관성 트래픽, 메모리 대역폭 포화, 그리고 암달의 법칙(직렬 구간이 상한을 만듦). 해법의 핵심은 공유를 없애는 것 — shared-nothing 액터/잡 큐 모델로 "한 룸은 한 스레드가 독점 처리"하면 락이 사라져 코어 수만큼 깔끔히 확장된다.

깊이 있는 설명

확장성을 깎는 원인들

  • 락 경합: 여러 코어가 한 락을 두고 줄을 서면 그 구간은 사실상 직렬. 경합이 심하면 스핀/블로킹·컨텍스트 스위칭 비용까지 더해진다.
  • false sharing: 서로 다른 코어가 논리적으론 다른 변수를 갱신해도 그 변수들이 같은 64B 캐시라인에 있으면, MESI 프로토콜이 라인 소유권을 코어 간에 계속 빼앗아(핑퐁) 성능이 급락.
  • 캐시 일관성 트래픽: 공유 쓰기가 많을수록 무효화(invalidation) 메시지가 코어 수에 비례해 늘어난다.
  • 메모리 대역폭: 모든 코어가 메모리를 두드리면 대역폭이 포화돼 코어를 더 줘도 못 쓴다.
  • 암달의 법칙: 전체 중 직렬 구간 비율이 s면 최대 속도향상은 1/s로 묶인다. 예: 직렬 5%면 코어가 무한이어도 최대 20배.

shared-nothing / 잡 큐(액터) 모델

  • 상태(룸/세션)를 소유자에게 가두고, 다른 스레드는 그 소유자의 큐에 **메시지(잡)**를 넣을 뿐 직접 건드리지 않는다.
  • 각 액터는 자기 큐를 단일 스레드로 순차 처리 → 락 불필요, 데이터 레이스 불가.
  • 코어마다 독립된 액터 집합을 배치하면 경합 없이 선형 확장에 가깝다.

틱 루프 설계

  • 룸마다 고정 주기(예: 30/60Hz)로 시뮬레이션을 한 스레드가 진행. 입력 패킷은 큐에 쌓였다가 틱 시작 시 일괄 적용.
  • 장점: 결정론·디버깅 용이·락 없음. 한계: 한 룸이 한 코어 성능에 묶임(아주 무거운 룸은 분할 필요), 룸 간 상호작용은 메시지 패싱이라 지연·복잡도 증가.

응용/실무 연결

  • MMO: 존을 코어/프로세스로 분할(shared-nothing), 존 경계 이동은 핸드오프 프로토콜.
  • 통계/카운터처럼 꼭 공유해야 하는 값은 Interlocked/코어별 누적 후 머지로 경합 제거.
  • 핫 카운터는 캐시라인 정렬(alignas(64))로 false sharing 차단.

흔한 오답·함정

  • "스레드를 많이 만들면 빨라진다" — 경합·스위칭으로 오히려 느려질 수 있다. 적정 병렬도는 코어 수 + 작업 성격(IO/CPU).
  • 락을 잘게 쪼개면 항상 좋다는 착각 — 락이 많아지면 락 순서·데드락·오버헤드가 늘고, 차라리 공유 제거가 낫다.
  • false sharing은 코드만 봐선 안 보인다 — perf c2c 같은 도구로 측정해야 드러남.

꼬리질문 대비

  • Q. 액터 모델의 단점? 룸 간 통신이 메시지 패싱이 되어 직접 호출보다 지연·복잡도↑, 그리고 한 액터가 한 코어 성능 한계에 묶임.
  • Q. NUMA가 확장성에 주는 영향? 다른 노드의 메모리 접근이 느리므로, 스레드와 데이터를 같은 NUMA 노드에 배치(affinity)해야 한다.
  • Q. 직렬 구간을 줄이는 현실적 방법? 전역 락 제거, 공유 카운터를 코어별로 분산, 시작/종료 같은 배리어 최소화.