저지연 트레이딩 시스템에서 CPU Cache가 중요한 이유 | Trading Engineering — Deep Dive #1
저지연 트레이딩 시스템에서 CPU Cache가 중요한 이유
Trading Engineering — Deep Dive #1
앞선 1차 연재에서는 Shared Memory, Lock 경합, Memory Copy, End-to-End Latency 등 저지연 트레이딩 시스템의 주요 요소들을 살펴보았습니다.
이제 한 단계 더 깊이 들어가 보겠습니다.
우리가 지금까지 이야기한 모든 데이터 처리는 결국 CPU가 데이터를 가져와서 처리하는 과정입니다.
그렇다면 한 가지 근본적인 질문이 생깁니다.
CPU는 실제로 데이터를 어떻게 읽고 있을까요?
현대의 저지연 시스템에서는 단순히 연산 속도만 빠르다고 좋은 성능이 나오는 것은 아닙니다.
CPU가 필요한 데이터를 얼마나 빠르게 가져올 수 있는가가 매우 중요합니다.
그리고 이 지점에서 CPU Cache가 중요한 역할을 합니다.
1. Main Memory만으로는 충분하지 않다
현대 CPU는 매우 빠르게 명령어를 실행할 수 있습니다.
하지만 CPU Core의 연산 속도에 비해 DRAM(Main Memory)에 접근하는 비용은 상대적으로 큽니다.
개념적으로 보면 다음과 같습니다.
저장 위치상대적인 접근 비용
| CPU Register | 매우 낮음 |
| L1 Cache | 수 Cycle 수준 |
| L2 Cache | L1보다 높음 |
| L3 Cache | L2보다 높음 |
| DRAM | 가장 높음 |
정확한 Cycle 수는 CPU 아키텍처와 상황에 따라 달라집니다.
중요한 것은 CPU Core와 가까운 메모리일수록 일반적으로 더 빠르다는 점입니다.
CPU가 필요한 데이터를 매번 DRAM에서 가져와야 한다면, 빠른 CPU도 메모리를 기다리는 시간이 발생할 수 있습니다.
그래서 현대 CPU에는 여러 단계의 Cache가 존재합니다.
2. CPU Cache Hierarchy
단순화하면 CPU의 메모리 구조는 다음과 같습니다.
CPU Core
↓
L1 Cache
↓
L2 Cache
↓
L3 Cache
↓
Main Memory (DRAM)
L1 Cache
- 가장 작음
- 가장 빠름
- 일반적으로 CPU Core별로 존재
L2 Cache
- L1보다 큼
- L1보다 느림
- 일반적으로 Core별로 존재
L3 Cache
- 더 큰 용량
- L1/L2보다 느림
- 일반적으로 여러 Core가 공유
즉, CPU가 자주 사용하는 데이터가 가까운 Cache에 존재한다면 매우 빠르게 접근할 수 있습니다.
반대로 필요한 데이터가 Cache에 없다면 더 아래 단계까지 내려가야 합니다.
3. Cache Line이라는 단위
여기서 중요한 개념이 하나 등장합니다.
Cache Line
CPU는 일반적으로 개별 변수 하나만 가져오는 것이 아니라 Cache Line 단위로 데이터를 가져옵니다.
많은 현대 CPU에서 Cache Line 크기는 64 bytes입니다.
예를 들어:
int price;
price는 4 bytes일 수 있지만 CPU가 해당 데이터를 Cache에 가져올 때는 주변 데이터를 포함한 Cache Line 단위로 가져올 수 있습니다.
즉:
Memory
[ 64-byte Cache Line ]
┌──────────────────────────────┐
│ price │ qty │ side │ ... │
└──────────────────────────────┘
와 같은 방식입니다.
이 때문에 데이터가 메모리에 어떻게 배치되어 있는가가 성능에 영향을 줄 수 있습니다.
4. Cache Miss가 왜 느린가?
CPU가 필요한 데이터를 Cache에서 찾지 못하면 Cache Miss가 발생합니다.
개념적으로는:
L1 Cache
↓ miss
L2 Cache
↓ miss
L3 Cache
↓ miss
DRAM
과 같은 과정을 거칠 수 있습니다.
이 과정에서 CPU는 필요한 데이터를 기다려야 합니다.
Cache Miss가 자주 발생하는 대표적인 상황은 다음과 같습니다.
- 큰 데이터 구조
- 불규칙한 메모리 접근
- 낮은 Data Locality
- 과도한 메모리 할당
- 여러 Core에서 동일한 데이터에 대한 빈번한 접근
특히 초당 수십만~수백만 개 이상의 메시지를 처리하는 시스템에서는 작은 차이도 누적될 수 있습니다.
5. Data Locality
CPU Cache를 이해할 때 중요한 개념이 Data Locality입니다.
쉽게 표현하면:
자주 사용하는 데이터를 서로 가깝게 배치하라.
예를 들어:
struct Order {
int qty;
int price;
int side;
};
처럼 실제로 함께 사용하는 데이터가 가까이 배치되어 있다면 CPU가 하나의 Cache Line에서 여러 데이터를 얻을 가능성이 높아집니다.
반대로 필요한 데이터가 메모리 곳곳에 흩어져 있다면 여러 Cache 접근이 필요할 수 있습니다.
따라서 저지연 시스템에서는 단순히 자료구조의 논리적인 구조뿐 아니라 메모리 배치 자체도 고려할 필요가 있습니다.
6. Multi-Core와 Cache Coherency
현대 트레이딩 시스템은 대부분 여러 CPU Core를 사용합니다.
예를 들어:
Core 1
↓
position += 1
Core 2
↓
position 읽기
와 같은 상황을 생각해 볼 수 있습니다.
두 Core가 서로 다른 Cache에 같은 데이터를 가지고 있다면, 데이터가 변경되었을 때 서로 다른 Core의 Cache가 일관된 상태를 유지해야 합니다.
이러한 메커니즘을 일반적으로:
Cache Coherency
라고 합니다.
문제는 여러 Core가 동일한 데이터를 계속 수정하면 Core 간 Cache 상태를 맞추기 위한 추가적인 트래픽이 발생할 수 있다는 것입니다.
즉,
공유 데이터가 많아질수록 CPU 내부에서도 데이터 이동 비용이 발생할 수 있습니다.
이 부분은 앞서 다룬 Lock Contention과도 연결됩니다.
7. False Sharing
CPU Cache를 이해하면 False Sharing이라는 재미있는 문제가 등장합니다.
예를 들어:
struct SharedData {
int counterA;
int counterB;
};
Thread A는:
counterA
를 계속 수정하고,
Thread B는:
counterB
를 계속 수정한다고 가정해 보겠습니다.
논리적으로는 서로 다른 변수입니다.
하지만 두 변수가 같은 Cache Line에 들어 있다면 문제가 발생할 수 있습니다.
Cache Line
┌──────────────────────────────────────┐
│ counterA │ counterB │ ... │
└──────────────────────────────────────┘
↑ ↑
Core 1 Core 2
Core 1이 counterA를 수정하고 Core 2가 counterB를 수정하면, 서로 다른 변수를 수정하고 있음에도 같은 Cache Line 때문에 Core 간 Cache 상태가 계속 영향을 받을 수 있습니다.
이것이 False Sharing입니다.
결과적으로:
- Cache Traffic 증가
- Core 간 통신 증가
- CPU 효율 저하
- Latency 증가
- Multi-thread 확장성 저하
등이 발생할 수 있습니다.
8. Cache와 Ring Buffer
앞선 연재에서 Ring Buffer를 다룬 적이 있습니다.
Ring Buffer는 저지연 시스템에서 자주 사용되는 구조입니다.
Producer
↓
Ring Buffer
↓
Consumer
이 구조를 CPU Cache 관점에서 보면 또 다른 최적화 포인트가 나타납니다.
Producer와 Consumer가 어떤 데이터를 자주 읽고 쓰는지에 따라:
- Cache Miss
- Cache Line 이동
- Cache Coherency
- False Sharing
등의 비용이 달라질 수 있습니다.
따라서 단순히 Ring Buffer를 사용한다고 끝나는 것이 아니라,
Ring Buffer의 데이터와 인덱스가 메모리에 어떻게 배치되어 있는가?
도 중요합니다.
9. Structure Alignment와 Padding
C 개발자라면 Alignment와 Padding도 익숙한 개념일 것입니다.
예를 들어:
struct Example {
char type;
int qty;
};
컴파일러는 CPU가 데이터를 효율적으로 접근할 수 있도록 구조체 내부에 Padding을 삽입할 수 있습니다.
즉, 실제 메모리 크기는 단순히:
1 + 4 = 5 bytes
가 아닐 수 있습니다.
메모리 정렬과 Padding은 단순히 구조체 크기의 문제가 아닙니다.
Cache Line과 함께 고려하면:
- 어떤 데이터가 같은 Cache Line에 들어가는지
- 어떤 데이터가 서로 다른 Cache Line으로 분리되는지
- False Sharing을 피할 수 있는지
에도 영향을 줄 수 있습니다.
10. CPU Cache와 Trading System
저지연 트레이딩 시스템을 단순화하면:
Market Data
↓
FEP
↓
Shared Memory
↓
Strategy
↓
Risk Check
↓
Order Manager
↓
DMA / FEP
와 같은 데이터 경로를 생각할 수 있습니다.
각 단계에서 다음 질문을 던져볼 수 있습니다.
- 어떤 데이터가 가장 자주 사용되는가?
- 데이터가 어느 Core에서 처리되는가?
- 여러 Core가 같은 데이터를 공유하는가?
- Cache Miss가 발생할 가능성이 높은가?
- Cache Line이 불필요하게 여러 Core 사이에서 이동하고 있지는 않은가?
- 데이터 구조가 Cache-friendly한가?
이런 질문은 단순히:
"더 빠른 CPU를 사용하면 되지 않을까?"
라는 접근보다 훨씬 깊은 수준의 성능 분석으로 이어집니다.
11. 실전에서 생각해야 할 원칙
저지연 시스템에서 CPU Cache를 고려한다면 다음과 같은 원칙을 생각할 수 있습니다.
Prefer
- 작은 데이터 구조
- Cache-friendly한 데이터 배치
- Sequential Memory Access
- 높은 Data Locality
- 명확한 Data Ownership
- Single Writer 구조
- 효율적인 Ring Buffer
Avoid
- 불필요한 데이터 공유
- Random Memory Access
- 지나치게 큰 데이터 구조
- Critical Path에서의 반복적인 Allocation
- False Sharing
- 불필요한 Cache Line 이동
결국 핵심은 간단합니다.
CPU가 필요한 데이터를 최대한 효율적으로 가져올 수 있도록 설계하는 것.
12. FontesFintech의 접근 방식
FontesFintech에서는 메모리를 단순한 저장 공간으로만 보지 않습니다.
저지연 시스템에서 메모리는 결국:
CPU 내부에서 데이터가 이동하는 경로
라고 볼 수 있습니다.
따라서 시스템 설계 단계에서 다음 요소들을 함께 고려합니다.
- Cache-aware Data Structure
- Shared Memory
- Ring Buffer
- Data Locality
- Data Ownership
- Synchronization Minimization
- Cache Traffic
- Memory Copy
- End-to-End Latency
알고리즘이 아무리 효율적이어도 CPU가 필요한 데이터를 기다리는 시간이 길다면 전체 시스템의 Latency는 낮아지기 어렵습니다.
결국 중요한 것은:
CPU가 필요한 데이터를 얼마나 효율적으로 가져오고 처리할 수 있는가
입니다.
Conclusion
CPU Cache는 저지연 시스템 설계에서 매우 중요한 기본 요소입니다.
CPU는 단순히 명령어를 실행하는 것이 아니라 끊임없이 데이터를 이동시키며 작업합니다.
개념적으로:
L1 → L2 → L3 → DRAM
이라는 메모리 계층을 통해 필요한 데이터를 가져옵니다.
따라서 데이터가 CPU 가까이에 있고, Cache에 잘 유지되며, 불필요한 Core 간 공유가 적을수록 효율적인 처리가 가능해집니다.
CPU Cache를 이해하면 앞서 살펴본:
- Memory Copy
- Shared Memory
- Lock Contention
- Ring Buffer
- False Sharing
- Multi-thread Performance
가 서로 어떻게 연결되는지도 이해할 수 있습니다.
그리고 다음 글에서는 여기서 한 단계 더 들어가 보겠습니다.
Trading Engineering — Deep Dive #2
False Sharing in Multithreaded Trading Systems
다음 글에서는 실제 C 코드와 함께:
Cache Line → Padding → Alignment → False Sharing → Multi-Core Performance
를 살펴보겠습니다.
In Low-Latency Systems, Data Placement Matters as Much as Code Execution.