도구스개발

캐시 라인 거짓 공유 계산기

구조체 필드가 어느 캐시 라인에 들어가는지 계산해 서로 다른 스레드가 같은 줄을 다투는 자리를 찾아냅니다. 갈라놓았을 때 늘어나는 메모리까지 함께 냅니다.

한 줄에 하나씩. 「long counter0」처럼 씁니다

이름을 쉼표로 나열합니다. 이 중 둘 이상이 같은 줄에 있으면 거짓 공유입니다

기기마다 다릅니다. 잘못 잡으면 갈라놓았다고 믿은 것이 여전히 같은 줄에 있습니다

0이면 제한 없음(기본 정렬)

거짓 공유가 일어날 수 있습니다

1개 줄에서 경합

구조체 64바이트 · 64바이트 줄 1개 · 지목한 필드 4개

바이트 범위든 필드경합
#00 ~ 63counter0, counter1, counter2, counter3, counter4, counter5, counter6, counter74개 ⚠
필드오프셋크기
counter0 long / int64_t / double08#0
counter1 long / int64_t / double88#0
counter2 long / int64_t / double168#0
counter3 long / int64_t / double248#0
counter4 long / int64_t / double328#0
counter5 long / int64_t / double408#0
counter6 long / int64_t / double488#0
counter7 long / int64_t / double568#0
지금 구조체 크기64바이트
지목한 필드를 모두 다른 줄에 놓으면320바이트
늘어나는 바이트+256바이트 (5배)
줄 경계에 걸친 필드없음
캐시는 바이트가 아니라 «줄» 단위로 오갑니다. 한 코어가 어떤 변수를 쓰면 그 변수가 든 64바이트 «전체»가 다른 코어의 캐시에서 무효가 됩니다. 그래서 두 스레드가 서로 다른 변수를 쓰는데 그 둘이 같은 줄에 있으면, 자료 경합이 하나도 없는데도 서로의 캐시를 계속 무효화합니다. 락도 없고 공유도 없는데 느려지는 이유이고, 코드를 아무리 들여다봐도 안 보이는 이유는 원인이 «메모리 배치»에 있기 때문입니다.
지금 경합하는 줄 #0(counter0, counter1, counter2, counter3) 입니다. 이 줄들에 지목한 필드가 둘 이상 들어 있어, 그 필드를 쓰는 스레드들이 서로의 캐시를 무효화합니다. 갈라놓으려면 필드 사이에 줄 크기만큼 패딩을 넣거나 줄 크기로 정렬합니다(C++의 alignas(64), Java의 @Contended).
갈라놓는 데도 값이 듭니다. 지목한 필드를 저마다 다른 줄에 놓으면 64바이트가 320바이트가 됩니다 — 5배입니다. 스레드마다 카운터 하나씩인 흔한 경우, 8바이트 카운터 여덟 개가 64바이트에서 512바이트가 됩니다. 캐시에 들어가는 양이 줄어들어 다른 곳에서 느려질 수 있으므로, 정말 자주 쓰는 필드만 갈라놓는 것이 낫습니다.
줄 크기가 늘 64바이트는 아닙니다. x86은 64바이트지만 애플 M 계열은 128바이트입니다. 64로 갈라놓았다고 믿은 변수가 128바이트 기기에서는 여전히 같은 줄에 있을 수 있습니다. 위에서 줄 크기를 바꿔 확인해 보세요.
얼마나 느려지는지는 재지 않습니다. 실제 비용은 쓰기 빈도·코어 수·소켓 구성·일관성 프로토콜에 달려 있어 메모리 배치만으로는 정해지지 않습니다. 이 계산기가 내는 것은 거짓 공유가 일어날 «수 있는» 자리이며, 실제로 그것이 병목인지는 프로파일러로 확인해야 합니다.
구조체 크기 자체가 궁금하다면 정렬·패딩 계산기가 따로 있습니다. 그쪽은 「구조체가 왜 필드 크기의 합보다 큰가」를 보고 필드 «순서»를 바꿔 크기를 줄이는 도구입니다. 이쪽은 반대로 패딩을 «넣어» 줄을 갈라놓습니다 — 물음도 답도 반대 방향입니다.

사용 방법

  1. 1구조체 필드를 한 줄에 하나씩 적습니다.
  2. 2서로 다른 스레드가 쓰는 필드의 이름을 나열합니다.
  3. 3쓰는 기기의 캐시 줄 크기를 고릅니다. x86은 64, 애플 M 계열은 128바이트입니다.
  4. 4경합하는 줄이 있는지 보고, 갈라놓았을 때의 메모리 비용을 확인합니다.

자주 묻는 질문

두 스레드가 서로 다른 변수를 쓰는데 그 변수들이 같은 캐시 라인에 있어서 생기는 성능 저하입니다. 캐시는 바이트 하나씩이 아니라 보통 64바이트인 줄 단위로 오갑니다. 한 코어가 어떤 변수를 쓰면 그 줄 전체가 다른 코어의 캐시에서 무효가 되므로, 자료 경합이 하나도 없는데도 서로의 캐시를 계속 무효화하게 됩니다.

원인이 코드가 아니라 메모리 배치에 있기 때문입니다. 락도 없고 공유 변수도 없는데 스레드를 늘릴수록 느려진다면 이것을 의심해야 합니다. 필드의 오프셋과 캐시 줄 크기를 함께 놓고 봐야 보입니다.

스레드마다 쓰는 변수를 각각 다른 캐시 라인에 놓으면 됩니다. 변수 사이에 줄 크기만큼 패딩을 넣거나 줄 크기로 정렬합니다. C++이라면 alignas(64), Java라면 @Contended, C라면 구조체에 더미 배열을 끼워 넣는 방법이 흔합니다.

메모리를 크게 씁니다. 8바이트 카운터 여덟 개가 64바이트에서 512바이트가 되어 여덟 배입니다. 캐시에 들어가는 양이 줄어 다른 곳에서 느려질 수 있으므로, 정말 자주 쓰는 필드만 갈라놓는 것이 낫습니다. 이 계산기는 그 비용을 함께 냅니다.

x86과 대부분의 ARM은 64바이트지만 애플 M 계열은 128바이트입니다. 이 값을 잘못 잡으면 64바이트로 갈라놓았다고 믿은 변수가 128바이트 기기에서는 여전히 같은 줄에 있습니다. 여러 기기에서 돌 코드라면 큰 쪽에 맞추는 편이 안전합니다.

물음이 반대입니다. 그쪽은 「구조체가 왜 필드 크기의 합보다 큰가」를 보고 필드 순서를 바꿔 크기를 줄이는 도구입니다. 이쪽은 같은 오프셋 계산 위에서 「어느 필드들이 같은 캐시 라인에 들어가는가」를 보고, 오히려 패딩을 넣어 줄을 갈라놓습니다.

그 필드 하나만 읽거나 써도 캐시 라인 두 개를 건드리게 됩니다. 다른 필드와 다투지 않더라도 캐시 미스가 두 배가 될 수 있습니다. 정렬을 맞추면 사라집니다.

알 수 없습니다. 실제 비용은 쓰기 빈도, 코어 수, 소켓 구성, 캐시 일관성 프로토콜에 달려 있어 메모리 배치만으로는 정해지지 않습니다. 이 계산기가 내는 것은 거짓 공유가 일어날 수 있는 자리이며, 실제로 그것이 병목인지는 프로파일러로 확인해야 합니다.

전송되지 않습니다. 계산은 모두 브라우저 안에서 이루어지고 넣은 값은 이 기기에만 남습니다.

알아두면 좋은 점

  • 거짓 공유가 일어날 수 있는 자리를 찾아 줍니다. 얼마나 느려지는지는 재지 않습니다.
  • 캐시 줄 크기는 기기마다 다릅니다. x86은 64, 애플 M 계열은 128바이트입니다.
  • 한 줄에 지목한 필드가 둘 이상 있어야 경합입니다. 하나뿐이면 다투지 않습니다.
  • 줄 경계에 걸친 필드는 혼자서도 줄 두 개를 건드립니다.
  • 갈라놓으면 메모리를 크게 씁니다. 늘어나는 양을 함께 냅니다.
  • 오프셋 계산은 구조체 정렬·패딩 계산기와 같은 규칙을 씁니다.

함께 보면 좋은 도구

마지막 검증: 2026년 9월 2일 · 결과는 참고용 추정치입니다.