Principles of Mechanical Sympathy
Quick Summary
이 글은 현대 하드웨어의 성능을 제대로 끌어내기 위해 메모리 접근 패턴, 캐시 라인, 단일 작성자 원칙, 자연 배칭을 이해하고 적용해야 한다고 설명합니다.
🖼️ 인포그래픽
🖼️ 4컷 인포그래픽
💡 한 줄 요약
이 글은 현대 하드웨어의 성능을 제대로 끌어내기 위해 메모리 접근 패턴, 캐시 라인, 단일 작성자 원칙, 자연 배칭을 이해하고 적용해야 한다고 설명합니다.
📌 핵심 요약
- 현대 하드웨어는 매우 빠르게 발전했지만, 소프트웨어는 여전히 콜드 스타트, ETL 파이프라인, 동시성 처리 같은 영역에서 하드웨어 성능을 충분히 활용하지 못하는 경우가 많습니다.
- 저자는 Martin Thompson이 소프트웨어 분야에 확산시킨 ‘기계적 공감’ 개념을 바탕으로, 하드웨어가 실제로 메모리를 읽고 쓰는 방식에 맞춰 소프트웨어를 설계해야 한다고 주장합니다.
- 첫 번째 원칙은 예측 가능하고 순차적인 메모리 접근입니다. CPU는 최근 접근한 메모리와 그 주변 메모리가 곧 다시 쓰일 것이라고 가정하기 때문에, 무작위 접근보다 선형 접근이 훨씬 유리합니다.
- 두 번째와 세 번째 원칙은 캐시 라인과 false sharing을 피하고, 쓰기 가능한 데이터나 리소스는 하나의 스레드가 소유하게 하는 것입니다. 이를 통해 동기화 비용, 락 경쟁, head-of-line blocking을 줄일 수 있습니다.
- 마지막으로 자연 배칭은 큐에 데이터가 생기면 즉시 배치를 시작하고 큐가 비거나 최대 배치 크기에 도달하면 실행하는 방식으로, 고정 타임아웃 기반 배칭보다 낮은 지연 시간을 달성할 수 있습니다.
🧩 주요 포인트
- 현대 하드웨어는 매우 빠르게 발전했지만, 소프트웨어는 여전히 콜드 스타트, ETL 파이프라인, 동시성 처리 같은 영역에서 하드웨어 성능을 충분히 활용하지 못하는 경우가 많습니다.
- 저자는 Martin Thompson이 소프트웨어 분야에 확산시킨 ‘기계적 공감’ 개념을 바탕으로, 하드웨어가 실제로 메모리를 읽고 쓰는 방식에 맞춰 소프트웨어를 설계해야 한다고 주장합니다.
- 첫 번째 원칙은 예측 가능하고 순차적인 메모리 접근입니다. CPU는 최근 접근한 메모리와 그 주변 메모리가 곧 다시 쓰일 것이라고 가정하기 때문에, 무작위 접근보다 선형 접근이 훨씬 유리합니다.
- 두 번째와 세 번째 원칙은 캐시 라인과 false sharing을 피하고, 쓰기 가능한 데이터나 리소스는 하나의 스레드가 소유하게 하는 것입니다. 이를 통해 동기화 비용, 락 경쟁, head-of-line blocking을 줄일 수 있습니다.
- 마지막으로 자연 배칭은 큐에 데이터가 생기면 즉시 배치를 시작하고 큐가 비거나 최대 배치 크기에 도달하면 실행하는 방식으로, 고정 타임아웃 기반 배칭보다 낮은 지연 시간을 달성할 수 있습니다.
🧠 상세 정리
1. 기계적 공감의 문제의식
글은 현대 하드웨어가 매우 빠르지만 소프트웨어가 그 속도를 충분히 활용하지 못한다는 문제 제기에서 출발합니다. 소비자용 GPU의 통합 메모리나 노트북에서 대규모 모델을 실행할 수 있는 신경망 엔진처럼 하드웨어는 크게 발전했지만, 간단한 서버리스 함수의 콜드 스타트나 CSV를 데이터베이스 행으로 바꾸는 ETL 작업은 여전히 느릴 수 있습니다. 저자는 이러한 간극을 Martin Thompson이 말한 ‘Mechanical Sympathy’의 부재로 설명합니다. 이는 레이싱 드라이버가 자동차의 동작 원리를 완전히 설계하지는 않아도 기계에 대한 감각을 가져야 한다는 말에서 빌려온 개념이며, 소프트웨어도 실행되는 하드웨어에 공감할 때 더 높은 성능을 낼 수 있다는 뜻입니다.
2. 하드웨어를 의식한 소프트웨어 설계
저자는 기계적 공감을 일상적으로 적용할 수 있는 원칙들로 정리합니다. 핵심은 예측 가능한 메모리 접근, 캐시 라인에 대한 이해, 단일 작성자 원칙, 자연 배칭입니다. 이 원칙들은 특정 규모나 특정 종류의 시스템에만 제한되지 않고, 단일 애플리케이션부터 분산 데이터 플랫폼까지 적용될 수 있다고 설명됩니다. 예시로 LMAX 아키텍처는 단일 Java 스레드에서 초당 수백만 이벤트를 처리하는 시스템으로 언급됩니다. 저자는 자신이 성능 민감 시스템, AI 추론 플랫폼, Protocol Buffers보다 빠른 바이너리 인코딩 등을 만들며 이 원칙들을 사용해 왔다고 소개합니다.
3. 예측 가능한 메모리 접근의 중요성
기계적 공감은 CPU가 메모리를 저장하고 접근하며 공유하는 방식을 이해하는 데서 시작됩니다. 현대 CPU는 레지스터와 버퍼, L1 캐시, L2 캐시, 여러 코어가 공유하는 L3 캐시, 그리고 RAM으로 이어지는 계층 구조를 사용합니다. 이 계층은 코어에 가까울수록 빠르지만 용량이 작고, 멀어질수록 느리지만 더 큰 저장 공간을 제공합니다. CPU는 작은 버퍼만으로는 모든 작업을 처리할 수 없기 때문에 더 느린 캐시나 메인 메모리에 자주 접근해야 합니다. 이를 보완하기 위해 CPU는 최근 접근한 메모리, 그 근처의 메모리, 그리고 반복되는 접근 패턴이 곧 다시 필요할 것이라고 예측합니다.
4. 무작위 접근보다 선형 접근이 빠른 이유
CPU의 예측 방식 때문에 메모리 접근 방식은 성능에 큰 차이를 만듭니다. 글은 실제로 선형 접근이 같은 페이지 안에서의 접근보다 낫고, 같은 페이지 안에서의 접근은 여러 페이지에 걸친 무작위 접근보다 훨씬 빠르다고 설명합니다. 따라서 알고리즘과 데이터 구조를 설계할 때 데이터가 예측 가능하고 순차적으로 접근되도록 만드는 것이 중요합니다. 예를 들어 ETL 파이프라인을 만들 때, 키마다 데이터베이스를 하나씩 조회하는 방식보다 전체 원본 데이터베이스를 순차적으로 스캔한 뒤 불필요한 키를 걸러내는 방식이 더 하드웨어 친화적일 수 있습니다. 이는 단순히 코드 수준의 미세 최적화가 아니라 데이터 처리 전략 자체의 선택과 관련됩니다.
5. 캐시 라인과 false sharing
CPU 캐시는 보통 캐시 라인이라는 연속된 메모리 덩어리 단위로 데이터를 읽고 씁니다. 캐시 라인은 일반적으로 2의 거듭제곱 크기를 가지며, 흔히 64바이트 길이입니다. 문제는 두 CPU가 서로 다른 변수를 쓰더라도 그 변수들이 같은 캐시 라인에 있으면 충돌이 발생할 수 있다는 점입니다. 이를 false sharing이라고 하며, 두 CPU가 실제로는 다른 변수를 다루고 있음에도 같은 캐시 라인에 대한 접근권을 두고 번갈아 기다리게 됩니다. 그 결과 로컬 캐시를 효율적으로 쓰지 못하고 공유 L3 캐시를 통해 동기화해야 하므로 지연 시간이 스레드 수 증가에 따라 크게 늘어날 수 있습니다.
6. false sharing을 피하는 방법과 주의점
글은 false sharing을 피하기 위해 낮은 지연 시간이 중요한 애플리케이션에서 캐시 라인에 빈 데이터를 채워 사실상 한 캐시 라인에 하나의 변수만 놓는 패딩 기법을 사용한다고 설명합니다. 패딩이 없으면 스레드가 추가될수록 false sharing으로 인한 지연 시간이 거의 선형적으로 증가할 수 있지만, 패딩을 적용하면 지연 시간이 거의 일정하게 유지될 수 있습니다. 중요한 점은 이 문제가 주로 쓰기 작업에서 발생한다는 것입니다. 읽기만 하는 경우 각 CPU가 캐시 라인을 로컬 캐시나 버퍼로 복사해도 다른 CPU의 복사본과 상태를 맞출 필요가 크지 않습니다. 그래서 여러 스레드가 공유하고 수정할 수 있는 atomic 변수 같은 자료형이 false sharing의 흔한 피해자가 될 수 있습니다.
7. 단일 작성자 원칙과 액터 모델
멀티스레드 시스템에서는 false sharing뿐 아니라 race condition, CPU 코어 수를 초과한 스레드로 인한 context switching 비용, mutex 같은 락의 부담도 문제가 됩니다. 저자가 가장 자주 사용하는 원칙은 단일 작성자 원칙입니다. 이는 어떤 데이터나 TCP 소켓 같은 리소스에 쓰기 작업이 발생한다면, 그 모든 쓰기를 하나의 스레드가 담당하게 하라는 원칙입니다. 글은 텍스트를 받아 임베딩을 생성하는 HTTP 서비스를 예로 들며, 여러 요청 스레드가 모델 실행을 위해 mutex를 두고 경쟁하면 head-of-line blocking에 빠질 수 있다고 설명합니다. 대신 모델 접근을 전담하는 액터 스레드를 두고, 요청 스레드들은 비동기 메시지를 보내도록 하면 락 경쟁을 제거하면서도 모델 호출을 단일한 흐름으로 제어할 수 있습니다.
8. 자연 배칭과 최적화의 전제
단일 작성자 원칙을 적용하면 액터가 여러 독립 요청을 하나의 배치 추론 호출로 묶을 수 있습니다. 여기서 배치를 만드는 방식으로 저자는 자연 배칭을 제안합니다. 미리 정한 배치 크기가 찰 때까지 기다리면 요청이 무한정 막힐 수 있고, 고정 간격으로 배치를 만들면 요청이 매번 일정 시간 기다려야 합니다. 자연 배칭은 큐에 요청이 생기면 즉시 배치를 만들기 시작하고, 최대 배치 크기에 도달하거나 큐가 비면 배치를 완료합니다. 글의 예시에서는 고정 타임아웃 방식보다 자연 배칭이 best-case와 worst-case 모두에서 두 배 좋은 지연 시간을 보입니다. 다만 저자는 이런 원칙을 적용하기 전에 관측 가능성을 우선하고, SLI, SLO, SLA를 정의해 어디를 개선해야 하며 언제 멈춰야 하는지 알아야 한다고 강조합니다.
🧾 핵심 주장 / 시사점
- 성능 문제는 단순히 하드웨어가 부족해서가 아니라, 소프트웨어가 메모리 계층과 캐시 동작, 쓰기 경쟁을 거스르는 방식으로 설계될 때 발생할 수 있습니다.
- 기계적 공감의 원칙들은 미세 최적화라기보다 데이터 접근 방식, 동시성 구조, 배치 처리 전략을 하드웨어 친화적으로 바꾸는 설계 원칙에 가깝습니다.
- 저자는 최적화보다 관측 가능성을 먼저 두어야 한다고 강조합니다. 측정 지표와 목표가 없으면 어떤 병목을 해결해야 하는지, 최적화를 언제 멈춰야 하는지 판단할 수 없기 때문입니다.
✅ 액션 아이템
- 콜드 스타트, ETL, 동시성 구간에서 메모리 접근을 예측 가능하고 순차적인 패턴으로 바꿔 하드웨어 캐시 이점을 선점한다.
- 캐시 라인 충돌과 false sharing 위험 구간을 찾아 쓰기 가능한 데이터는 단일 스레드가 소유하도록 재설계해 동기화 비용을 낮춘다.
- 고정 타임아웃 배칭 대신 큐 적재 즉시 시작하고 큐 소진 또는 최대치 도달 시 즉시 실행하는 자연 배칭으로 지연을 낮춘다.
❓ 열린 질문
- 메모리 성능 병목이 큰 영역은 콜드 스타트, ETL, 동시성 중 어디이며 우선 전환 대상은 어떻게 가려야 하는가?
- 쓰기 가능 리소스의 단일 스레드 소유 전환이 락 경쟁과 head-of-line blocking을 실제로 얼마나 줄일 수 있는가?
- 자연 배칭의 큐 임계값 기준은 고정 타임아웃 대비 latency 최적화를 위해 어떤 방식으로 결정할 수 있는가?