캐시 일관성 프로토콜과 다중 코어 데이터 동기화

다중 코어 환경에서 캐시 사본이 달라지는 원인과 MESI·MOESI·디렉터리 기반 캐시 일관성 프로토콜의 동작 및 성능 고려사항을 정리한다.

2026-08-14 · 최초 발행 2026-01-04

캐시마다 복사된 값이 달라질 때

다중 코어 프로세서에서는 코어별로 독립 캐시가 존재하고, 같은 메모리 위치가 여러 캐시에 복사될 수 있다. 한 코어가 값을 바꾼 뒤 다른 코어가 이전 복사본을 계속 읽으면 데이터 불일치가 생긴다. 캐시 일관성 프로토콜은 하드웨어가 이 복사본들을 조정해 모든 캐시가 동일한 데이터를 보도록 만드는 방식이다.

초기 상태: X = 0Core 1 캐시: X = 0Core 2 캐시: X = 0Core 1: X = 1 쓰기Core 1 캐시: X = 1Core 2 캐시: X = 0(불일치!)

문제는 동시 접근 방식에 따라 여러 형태로 나타난다.

  • Write-Write 충돌: 두 코어가 같은 위치에 동시에 쓰는 경우
  • Read-Write 충돌: 한 코어의 읽기와 다른 코어의 쓰기가 겹치는 경우
  • Stale Data: 한 캐시에서 수정한 값이 다른 캐시에 반영되지 않는 경우
  • Lost Update: 동시 쓰기 때문에 일부 수정이 사라지는 경우
  • 순서 문제: 코어마다 메모리 쓰기 순서를 다르게 관찰하는 경우

일관성은 하나의 코어만 쓰기를 수행하도록 하고(Single Writer, Multiple Readers), 모든 코어가 같은 순서로 메모리 연산을 관찰하게 하는 순차 일관성(Sequential Consistency)과 연결된다. 읽기는 가장 최근 쓰기 결과를 반환해야 하며, 쓰기는 모든 코어에 원자적으로 반영되어야 한다. 이 과정이 하드웨어에서 처리되므로 프로그래머는 일관성 자체를 매번 의식하지 않고 코드를 작성할 수 있다.

버스를 감시하는 스누핑 방식

스누핑 기반 프로토콜에서는 모든 캐시가 공유 버스의 트래픽을 감시한다. 캐시 연산은 버스에 브로드캐스트되고, 각 캐시는 다른 캐시의 요청을 감지해 자기 캐시 라인의 상태를 즉시 바꾼다.

Core 1Cache 1Core 2Cache 2Core 3Cache 3공유 버스Main Memory모든 캐시가 버스 감시

이 방식은 반응이 빠르지만 모든 트랜잭션을 방송하므로 버스 대역폭이 병목이 된다. 코어 수는 8-16개 수준으로 제한적이며, 모든 캐시가 계속 버스를 감시해야 하므로 전력 소비도 발생한다.

대표 상태 프로토콜은 다음과 같다.

  • MSI: Modified, Shared, Invalid의 3상태
  • MESI: Modified, Exclusive, Shared, Invalid의 4상태
  • MOSI: Modified, Owned, Shared, Invalid
  • MOESI: Modified, Owned, Exclusive, Shared, Invalid의 5상태
  • MESIF: Intel 변형으로 Forward 상태를 추가

MESI가 캐시 라인을 다루는 방식

MESI는 캐시 라인을 Invalid, Shared, Exclusive, Modified 상태로 구분한다.

Invalid (I)캐시 라인 없음 또는 무효Shared (S)읽기 전용, 다른 캐시에도 존재Exclusive (E)읽기/쓰기 가능, 유일한 복사본Modified (M)수정됨, 유일한 복사본,메모리와 불일치

Invalid 상태에서는 데이터가 없으므로 접근할 때 버스 트랜잭션이 필요하다. Shared는 읽기만 가능하며, 쓰기 전에 다른 캐시의 복사본을 무효화해야 한다. Exclusive는 메모리와 일치하는 유일한 복사본이므로 쓰기를 해도 버스 트랜잭션이 필요 없다. Modified는 메모리와 달라진 더티 데이터의 유일한 복사본이며, 교체 시 메모리에 기록해야 한다.

읽기(유일)읽기(공유)쓰기다른 코어 읽기쓰기다른 코어 쓰기다른 코어 쓰기다른 코어 읽기InvalidExclusiveSharedModified

버스에서는 읽기 요청(Read), 쓰기 권한을 얻기 위한 읽기(Read for Ownership, RFO), 다른 캐시 라인을 지우는 Invalidate, 더티 데이터를 메모리에 쓰는 Writeback, 다른 캐시가 데이터를 제공하는 Intervention이 발생한다.

다음은 두 코어가 같은 데이터를 읽고 쓰는 상태 변화다.

초기: 모든 캐시 Invalid

(1) Core 1 읽기:
    Core 1: I → E (버스 읽기, 다른 캐시 없음)

(2) Core 2 읽기:
    Core 1: E → S (다른 코어 요청 감지)
    Core 2: I → S (버스 읽기, 공유 감지)

(3) Core 1 쓰기:
    Core 1: S → M (버스 무효화 방송)
    Core 2: S → I (무효화 수신)

(4) Core 2 읽기:
    Core 1: M → S (더티 데이터 제공)
    Core 2: I → S (개입된 데이터 수신)

MOESI의 Owned 상태가 줄이는 메모리 기록

MOESI는 MESI에 Owned 상태를 더한다. Owned는 더티 데이터이면서 다른 캐시와 공유할 수 있는 상태이며, 해당 캐시가 메모리 기록 책임을 가진다.

Owned (O)공유되지만 책임 있는 상태더티 데이터이지만 다른 캐시와공유메모리 기록 책임다른 캐시 요청 제공

Modified 상태의 데이터가 다른 코어의 읽기 요청으로 Shared가 될 때, MOESI는 즉시 메모리에 기록하지 않아도 된다. 원래 데이터를 보유한 캐시는 Owned가 되고 다른 캐시에 데이터를 제공한다. 이로써 Writeback을 지연하고 메모리 접근 및 대역폭 사용을 줄인다. AMD는 Opteron, Ryzen 등에서 MOESI를 사용하며, 공유 쓰기 패턴에서 확장성을 높이는 방식으로 활용한다.

(1) Core 1: M (더티)
(2) Core 2 읽기:
    Core 1: M → O (더티이지만 공유)
    Core 2: I → S
    메모리: 여전히 오래된 값 (기록 안 함)

(3) Core 3 읽기:
    Core 1: O (여전히 책임)
    Core 3: I → S
    Core 1이 데이터 제공

(4) Core 1 교체:
    Core 1: O → I (메모리에 Writeback)

코어가 늘어나면 디렉터리가 필요한 이유

스누핑은 모든 요청을 방송하기 때문에 16개 이상 코어에서는 비효율적이다. 버스 없는 크로스바나 링 토폴로지에도 맞지 않고, 분산 메모리를 쓰는 NUMA 시스템에서는 더 큰 제약이 된다.

디렉터리 기반 프로토콜은 각 메모리 블록에 대한 캐시 상태와 공유자 목록을 디렉터리에 기록한다. 요청이 들어오면 모든 캐시에 방송하지 않고 관련된 캐시에만 포인트-투-포인트 메시지를 보낸다.

메모리 블록디렉터리 엔트리상태 정보공유자 리스트Core 1 캐시Core 2 캐시Core 3 캐시포인트-투-포인트 메시지

디렉터리는 메모리와 함께 두거나 별도 위치에 둘 수 있다. 수십-수백 코어까지 지원할 수 있고, 링·메시·트리 등 다양한 토폴로지에 적용할 수 있다. 불필요한 브로드캐스트와 캐시 활성화를 줄여 대역폭 및 전력 효율을 높이지만, 디렉터리 저장 공간과 관리 로직이라는 오버헤드가 따른다. NUMA 환경에서는 로컬 메모리와 원격 메모리를 구분해 관리하는 데도 적합하다.

일관성 트래픽을 줄이는 코드와 메모리 모델

서로 다른 변수가 같은 캐시 라인에 놓인 상태에서 각 코어가 각각의 변수를 갱신하면, 실제 데이터 충돌이 없어도 캐시 라인 전체가 무효화된다. 이를 False Sharing이라 한다.

캐시 라인 (64바이트)변수 A (4바이트)변수 B (4바이트)Core 1: A 수정전체 라인 무효화Core 2: B 수정불필요한 일관성 트래픽

False Sharing은 변수 사이에 64바이트 패딩을 두고, 독립 변수들을 서로 다른 캐시 라인에 정렬하며, 쓰레드별 데이터를 분리하는 구조체 설계로 피할 수 있다. 컴파일러 속성으로는 __attribute__((aligned(64)))를 사용할 수 있다. 불필요한 무효화를 없애면 확장성이 개선된다.

프리페치된 데이터도 MESI 등의 일관성 상태를 가진다. 하드웨어 프리페치와 명시적 소프트웨어 프리페치는 일관성 프로토콜과 함께 동작해야 하며, 투기적 로드는 무효화되면 프리페치를 취소한다. 과도한 프리페치는 일관성 트래픽을 늘릴 수 있으므로 대역폭과의 균형이 필요하다.

메모리 배리어는 코어가 관찰하는 일관성 메시지 순서를 강제한다. x86은 강한 모델이고 ARM/POWER는 약한 모델이며, MFENCE, DMB, SYNC 같은 배리어 명령어를 사용한다. 배리어는 비용이 크지만 락 프리 알고리즘의 동기화에는 필수다.

프로세서별 구현에서 보이는 차이

Intel의 MESIF는 Shared 상태 중 하나에 Forward 역할을 부여해 응답 책임을 맡긴다. 여러 캐시가 동시에 응답하는 경쟁을 줄이며, QPI/UPI 링 인터커넥트로 확장한다. 대규모 Xeon에는 디렉터리 하이브리드가 추가되고, TSX는 트랜잭션 메모리로 일관성을 최적화한다.

AMD는 MOESI의 Owned 상태로 Writeback을 지연한다. Infinity Fabric을 사용하고 CCX/CCD 기반 Chiplet 구조로 확장하며, 각 CCX는 독립적인 L3 캐시를 가진다. Ryzen은 단일 NUMA 노드이고 Threadripper/EPYC는 다중 노드다.

ARM은 일반적으로 MOESI를 사용하며 약한 메모리 모델 때문에 명시적 배리어가 필요하다. ACE 프로토콜은 ARM Cache Coherent Interconnect를 제공하고, DSU는 DynamIQ Shared Unit으로 일관성을 관리한다. 빅리틀 클러스터 사이의 일관성을 유지하면서 모바일 환경의 전력 효율을 중심에 둔다.

캐시 일관성MESIMOESI멀티코어운영체제