14. 거짓 공유(false sharing)와 캐시 라인 정렬
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C++
// ─────────────────────────────────────────────────────────────
// 주제: 거짓 공유(false sharing)와 캐시 라인 정렬
//
// 게임 서버의 워커들이 서로 다른 카운터(처리량/통계)를 atomic 으로 증가시킨다.
// 아래 두 레이아웃 Counters / PaddedCounters 가 있다.
//
// 과제(실행 전에 예측하라):
// 1) 아래 4개의 출력값(sizeof/alignof, Counters 와 PaddedCounters)을 예측하라.
// 2) 스레드 A는 .a 만, 스레드 B는 .b 만 각각 1억 번 증가시킨다고 하자.
// 두 레이아웃 중 어느 쪽이 빠른가? 그 이유를 "캐시 라인"과 캐시 일관성
// 프로토콜(예: MESI) 관점에서 설명하라.
// 3) PaddedCounters 가 메모리를 더 쓰는데도 쓰는 이유와, 적정 패딩 크기를
// 무엇으로 정해야 하는지 설명하라.
// (내부 동작 설명은 answer.md 에서 다룬다. 여기서는 예측만.)
// ─────────────────────────────────────────────────────────────
#include <atomic>
#include <cstddef>
#include <cstdio>
#include <thread>
#include <chrono>
struct Counters {
std::atomic<long> a{0};
std::atomic<long> b{0};
};
struct alignas(64) PaddedCounters {
alignas(64) std::atomic<long> a{0};
alignas(64) std::atomic<long> b{0};
};
template <class T>
double bench(T& c) {
constexpr long N = 100'000'000;
auto t0 = std::chrono::steady_clock::now();
std::thread ta([&]{ for (long i = 0; i < N; ++i) c.a.fetch_add(1, std::memory_order_relaxed); });
std::thread tb([&]{ for (long i = 0; i < N; ++i) c.b.fetch_add(1, std::memory_order_relaxed); });
ta.join(); tb.join();
auto t1 = std::chrono::steady_clock::now();
return std::chrono::duration<double, std::milli>(t1 - t0).count();
}
int main() {
printf("sizeof(Counters)=%zu alignof(Counters)=%zu\n",
sizeof(Counters), alignof(Counters));
printf("sizeof(PaddedCounters)=%zu alignof(PaddedCounters)=%zu\n",
sizeof(PaddedCounters), alignof(PaddedCounters));
Counters c; double t1 = bench(c);
PaddedCounters p; double t2 = bench(p);
printf("shared : %.0f ms (a=%ld b=%ld)\n", t1, c.a.load(), c.b.load());
printf("padded : %.0f ms (a=%ld b=%ld)\n", t2, p.a.load(), p.b.load());
return 0;
} 내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.