거짓 공유 (False Sharing)
핵심 인사이트 (3줄 요약)
- 본질: 멀티스레드 프로그래밍에서 두 개 이상의 스레드가 논리적으로 완전히 다른 독립된 변수를 수정함에도 불구하고, 그 변수들이 물리적으로 같은 **'캐시 라인(Cache Line, 64바이트)'**에 위치하여 하드웨어의 무효화(Invalidate) 로직을 불필요하게 자극하는 현상이다.
- 가치: 스레드 간 락(Lock) 경합이 전혀 없음에도 불구하고 캐시 미스(Cache Miss)가 연쇄 폭발하는 스래싱(Thrashing)을 유발하여, 코어를 늘렸는데도 시스템 성능이 단일 코어보다 오히려 수십 배 느려지게 만드는 최악의 '보이지 않는 병목'이다.
- 융합: 이를 해결하기 위해 소프트웨어 엔지니어는 데이터 구조체 사이에 의미 없는 빈 공간을 밀어 넣는 '메모리 패딩(Padding, alignas)' 기법을 적용하여, 논리적 코드와 하드웨어 캐시 아키텍처를 강제로 일치시키는 융합적 사고를 발휘해야 한다.
Ⅰ. 개요 및 필요성 (Context & Necessity)
거짓 공유 (False Sharing)는 멀티코어 환경에서 하드웨어 설계의 사각지대를 소프트웨어 개발자가 무심코 밟았을 때 터지는 가장 파괴적인 지뢰다.
개발자가 스레드 2개를 띄워서 각각 완전히 다른 변수인 var_A와 var_B를 계산하게 했다. 두 스레드는 서로의 데이터를 건드리지 않으므로(독립성), 락(Lock)을 걸 필요가 없다고 안심한다.
하지만 칩 내부에 있는 하드웨어 캐시 컨트롤러(MESI 프로토콜)의 눈에는 변수 A와 B가 보이지 않는다. 오직 **"64바이트짜리 덩어리(캐시 라인)"**만 보일 뿐이다.
[거짓 공유(False Sharing)가 유발하는 캐시 무효화 폭풍의 원리]
* 변수 구조체 메모리 배치 상태: [ var_A(4 Byte) | var_B(4 Byte) | ... ]
-> 두 변수가 우연히 딱 붙어있어 64 Byte 캐시 라인 1개에 통째로 묶임.
1. 코어 0(스레드 A)이 var_A를 +1 수정함.
-> 하드웨어: "이 캐시 라인(64B 전체) 수정됐다! 딴 놈들 캐시 다 지워라!(Invalidate)"
2. 코어 1(스레드 B)이 var_B를 수정하려 함.
-> 어라? var_A 때문에 덩달아 내 캐시 라인도 파괴됐네? (억울한 Cache Miss 발생)
-> 메모리(L3)에서 100ns 걸려서 다시 가져옴. 그리고 var_B 수정!
-> 하드웨어: "캐시 라인 수정됐다! 코어 0 캐시 지워라!"
3. 코어 0이 var_A를 또 수정하려 함.
-> 코어 1 때문에 내 캐시가 또 파괴됨 (억울한 Cache Miss 또 발생)
=> 결과: 아무런 논리적 공유가 일어나지 않았는데("False" Sharing),
하드웨어의 무식한 블록 관리 때문에 두 코어가 1초에 백만 번씩
서로의 캐시를 부수고 다시 가져오는 미친 핑퐁(Ping-pong) 상태에 빠짐.
이는 개발자가 짠 코드는 100점이지만, 하드웨어가 0점으로 반응하는 대참사다. 이 눈에 보이지 않는 병목을 잡지 못하면, 64코어 서버가 1코어 노트북보다 느리게 도는 환장할 노릇이 벌어진다.
📢 섹션 요약 비유: 거짓 공유는 기숙사에서 두 학생(A, B)이 각자 자기 일기를 쓰는데, 우연히 하나의 일기장(캐시 라인)의 왼쪽 페이지와 오른쪽 페이지를 나눠 쓰게 된 상황입니다. A가 일기를 고칠 때마다 규칙상 일기장을 뺏어서 지우개로 빡빡 지우니, B도 억울하게 일기를 다시 써야 해서 밤새도록 싸우고 아무 일도 못 하는 겁니다.
Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)
거짓 공유를 이해하려면, 현대 CPU 아키텍처가 왜 데이터를 변수 단위가 아닌 '캐시 라인(Cache Line)' 단위로 뭉뚱그려 관리할 수밖에 없는지를 알아야 한다.
| 아키텍처 요소 | 하드웨어 동작 원리 | 거짓 공유와의 상관관계 | 비유 |
|---|---|---|---|
| Cache Line (64 Byte) | 메인 메모리와 캐시가 데이터를 주고받는 최소 물리적 배송 단위 | 변수가 4바이트라도 무조건 주변 60바이트를 같이 퍼옴 (공간적 지역성) | 사과 하나를 사도 무조건 1박스로 배송 |
| MESI Protocol | 코어 간 데이터 불일치를 막는 스누핑 하드웨어 방어막 | 변수 단위가 아니라 캐시 라인 단위로 M, S, I 상태를 부여함 | 박스 안에 사과 하나만 썩어도 박스 전체를 폐기함 |
| Directory Controller | 64코어 이상에서 캐시 라인의 소유주를 추적하는 중앙 장부 | 거짓 공유 발생 시 디렉터리에 트래픽이 폭주하여 버스 대역폭 100% 마비 (Snoop Storm) | 박스 주인이 바뀔 때마다 본사에 일일이 전화해서 보고함 |
거짓 공유를 해결하는 유일한 물리적 방법은, 하드웨어가 "얘네 둘은 다른 캐시 라인이구나!"라고 인식하게 만드는 것이다. 즉, 메모리를 고의로 낭비(Padding)해야 한다.
[메모리 패딩(Padding)을 통한 캐시 라인 강제 분리(Tearing)]
(A) 재앙의 코드 (거짓 공유 100%)
struct Counter {
int threadA_count; // 0~3 Byte
int threadB_count; // 4~7 Byte
}; // 총 8 Byte -> 무조건 1개의 캐시 라인(64B)에 같이 들어감. (지옥문 개방)
(B) 구원의 코드 (메모리 패딩 적용)
struct Counter {
alignas(64) int threadA_count; // 강제로 64바이트 경계(Boundary)에서 시작하게 만듦!
alignas(64) int threadB_count; // 다음 64바이트 경계로 밀어버림!
};
// A는 캐시 라인 1번 방에, B는 캐시 라인 2번 방에 완벽히 격리됨.
=> 하드웨어 결과: 코어 0이 A를 백만 번 수정해도 캐시 라인 1번만 무효화됨.
캐시 라인 2번에 있는 B는 평생 평화롭게 [E 상태(독점)]를 유지하며 빛의 속도로 연산 완료!
이 코드는 RAM 용량을 낭비하는 것처럼 보이지만, 1바이트를 아끼려다 코어 64개를 멈춰 세우는 멍청한 짓을 막는 궁극의 "공간과 시간의 트레이드오프(Space-Time Tradeoff)" 최적화다.
📢 섹션 요약 비유: 두 학생이 한 공책에서 싸우는 걸 막는 유일한 방법은 공책을 아끼지 말고, A학생은 1번 공책에, B학생은 아예 텅 빈 새 2번 공책(Padding)에 쓰게끔 완전히 분리해 주는 것입니다. 종이(메모리)는 조금 낭비되지만 싸움(지연)은 1초 만에 끝납니다.
Ⅲ. 실무 적용 및 기술사적 판단 (Strategy & Decision)
실무에서 Java/C++로 멀티스레드 병렬 처리를 할 때, 성능 테스트 벤치마크가 곤두박질치면 가장 먼저 의심해야 할 용의자 1순위가 바로 거짓 공유다.
실무 성능 최적화 및 핫스팟 제거 시나리오
-
Java 8
@Contended어노테이션을 활용한 무결점 동시성- 상황: 멀티스레드 환경에서 Java의
ConcurrentHashMap을 구현할 때, 스레드들이 맵의size카운터를 동시에 올리면 극심한 거짓 공유 핑퐁이 터짐. - 의사결정: 자바 개발자들은 카운터를 하나의 변수로 두지 않고 여러 개로 찢어놓은
LongAdder같은 클래스를 사용한다. 이 클래스의 내부 셀(Cell)들은@Contended라는 마법의 어노테이션이 붙어있어, JVM이 기동 할 때 알아서 셀들 사이에 64바이트 더미(Dummy) 패딩 빈칸을 쫙 깔아준다. - 이유: OS나 언어가 알아서 해주겠지 믿지 마라. 개발자가 구조적으로 데이터를 찢고(Striping), 하드웨어 캐시 라인을 회피하도록 힌트를 주어야만 진정한 TLP(스레드 병렬성)가 달성된다.
- 상황: 멀티스레드 환경에서 Java의
-
C/C++ NUMA 서버에서의 거짓 공유 색출 (
perf c2c)- 상황: 리눅스 대형 서버에서 커스텀 스레드 풀(Thread Pool)의 큐(Queue) 연산 속도가 비정상적임.
- 의사결정: 리눅스 프로파일러
perf c2c (Cache-to-Cache)를 실행하여, HITM (Hit on Modified) 지표가 유독 높은 캐시 라인 주소를 찾아낸다. 코드를 까보면 백발백중 스레드 0이 쓰는head포인터와 스레드 1이 쓰는tail포인터가 나란히 붙어있다. 이를alignas(64)로 찢는다. - 이유:
HITM은 "내 캐시에 없어서 남의 캐시에서 훔쳐왔다"는 뜻이다. 남의 캐시에 있다는 것은 누군가 나와 같은 캐시 라인을 건드려 M(수정) 상태로 락을 걸었다는 의미다. 거짓 공유를 탐지하는 가장 과학적이고 유일한 리눅스 커널 분석 기법이다.
📢 섹션 요약 비유: 거짓 공유는 암과 같습니다. 겉(코드)으로는 아무 문제가 없는데 속(하드웨어 캐시)에서 세포들이 서로를 파괴하고 있습니다. perf c2c라는 X-ray로 암세포(공유 캐시 라인)를 찾아내어, alignas(패딩)라는 메스로 두 변수 사이의 살을 잘라버려 완전히 격리해야 환자가 살아납니다.