
저지연 트레이딩 시스템을 위한 C++
가장 빠른 트레이딩 회사들이 모든 것을 C++로 작성하는 이유
C++가 속도의 언어인 이유
지연 시간에 민감한 스택에서 C++는 여전히 매칭 경로를 지배합니다: Globex 스타일 거래소, 다수의 주식 피드, 크립토 엔진(Hyperliquid급 인프라 포함)은 모두 GC(가비지 컬렉션) 정지가 중간에 숨어 있지 않은, 예측 가능한 기계어 코드로 컴파일됩니다.
무엇이 C++를 저지연 트레이딩에 유독 적합하게 만드는가?
- 가비지 컬렉터가 없음 — 예측 불가능한 정지가 없습니다. 메모리가 언제 할당되고 해제되는지를 정확히 제어합니다.
- 하드웨어와의 근접성 — CPU 캐시 라인, SIMD 명령어, 메모리 매핑 I/O, 커널 바이패스 네트워킹에 직접 접근합니다.
- 컴파일 타임 연산 — 템플릿 메타프로그래밍이 작업을 런타임에서 컴파일 타임으로 옮겨, 손으로 작성한 어셈블리만큼 빠른 코드를 만들어냅니다.
- 예측 가능한 지연 시간 — 신중한 코딩을 거치면 지터를 최소화한 서브마이크로초 수준의 틱-투-트레이드 지연 시간을 달성할 수 있습니다.
현실 점검: 가장 빠른 데스크의 틱-투-트레이드 예산은 흔히 서브마이크로초입니다. 하나의 어긋난 할당이나 캐시 미스가 전체 예산을 다 먹어버릴 수 있습니다 — 그래서 팀들은 핫 패스를 생산 라인처럼 지킵니다.
메모리 관리: 스택, 힙, 그리고 커스텀 할당자
저지연 C++에서는 무엇을 계산하는지보다 메모리를 어떻게 할당하는지가 더 중요합니다. 스택 할당과 힙 할당의 차이는 지연 시간에서 100배가 될 수 있습니다.
// Stack allocation — near-instant, deterministic
struct OrderUpdate {
uint64_t order_id;
double price;
uint32_t quantity;
char side; // 'B' or 'S'
};
void process_tick(const MarketData& tick) {
OrderUpdate update{}; // Stack — zero allocation cost
update.price = tick.mid_price();
// ... process on the hot path
}
// Heap allocation — slow, non-deterministic (avoid on hot path)
auto* order = new OrderUpdate{}; // Calls malloc — BAD on hot path
프로덕션 시스템은 핫 패스가 절대 범용 힙을 호출하지 않도록 커스텀 할당자에 의존합니다:
- 풀 할당자(Pool allocators) — 큰 블록을 미리 할당하고 고정 크기 조각을 잘라냅니다. 파편화가 없고 O(1) 할당입니다.
- 아레나 할당자(Arena allocators) — 각 할당마다 포인터를 앞으로 밀고, 한꺼번에 전부 해제합니다. 메시지 단위 처리에 완벽합니다.
- 휴지 페이지(Huge pages) — 2MB 또는 1GB 페이지는 TLB 미스를 줄여주며, 오더북 데이터가 메가바이트 단위로 걸쳐 있을 때 중요합니다.
현대 C++는 RAII(Resource Acquisition Is Initialization)와 스마트 포인터로 안전한 메모리 관리를 인체공학적으로 만듭니다:
// RAII — resource lifetime tied to scope
{
auto connection = std::make_unique<TcpConnection>(endpoint);
connection->send(order_message);
} // Connection automatically closed here
// Shared ownership for reference-counted resources
auto config = std::make_shared<TradingConfig>(load_config());
engine.set_config(config); // Multiple owners, automatic cleanup락 프리 데이터 구조와 동시성
뮤텍스는 저지연 코드의 적입니다. 단 한 번의 std::mutex::lock() 호출은 경합이 없어도 20~100나노초가 걸릴 수 있으며 — 경합이 있으면 스레드를 마이크로초 단위로 정지시킬 수 있습니다. 트레이딩 시스템은 대신 락 프리(lock-free) 데이터 구조를 사용합니다.
트레이딩에서 가장 중요한 락 프리 구조는 단일 생산자 단일 소비자(SPSC) 큐입니다:
template<typename T, size_t Capacity>
class SPSCQueue {
alignas(64) std::array<T, Capacity> buffer_;
alignas(64) std::atomic<size_t> head_{0};
alignas(64) std::atomic<size_t> tail_{0};
public:
bool try_push(const T& item) {
const auto tail = tail_.load(std::memory_order_relaxed);
const auto next = (tail + 1) % Capacity;
if (next == head_.load(std::memory_order_acquire))
return false; // Full
buffer_[tail] = item;
tail_.store(next, std::memory_order_release);
return true;
}
bool try_pop(T& item) {
const auto head = head_.load(std::memory_order_relaxed);
if (head == tail_.load(std::memory_order_acquire))
return false; // Empty
item = buffer_[head];
head_.store((head + 1) % Capacity, std::memory_order_release);
return true;
}
};
핵심 설계 원칙:
alignas(64)— 각 원자 변수가 자신만의 캐시 라인을 가져, CPU 코어 간의 거짓 공유(false sharing)를 방지합니다.- 메모리 순서(Memory ordering) —
acquire/release세맨틱은seq_cst보다 저렴하며 생산자-소비자 패턴에 충분합니다. - 2의 거듭제곱 크기 — 프로덕션에서는 1024나 4096 같은 크기를 사용해 모듈로 연산을 비트단위 AND로 만듭니다.
일반적인 배선 방식: NIC 스레드가 인큐하고, 전략 스레드가 디큐합니다 — 토폴로지를 제대로 잡으면 전송 경로에 뮤텍스가 필요 없습니다.
템플릿: 컴파일 타임 연산
C++ 템플릿은 작업을 런타임에서 컴파일 타임으로 옮길 수 있게 해줍니다. 트레이딩에서 이는 여러분의 바이너리가 거래하는 정확한 프로토콜, 상품, 전략에 특화된다는 뜻입니다 — 핫 패스에는 런타임 분기가 없습니다.
// Compile-time FIX protocol field parsing
template<int Tag>
struct FIXField;
template<> struct FIXField<35> { // MsgType
static constexpr const char* name = "MsgType";
using type = char;
};
template<> struct FIXField<44> { // Price
static constexpr const char* name = "Price";
using type = double;
};
// Zero-overhead dispatch based on message type
template<char MsgType>
void handle_message(const char* raw, size_t len) {
if constexpr (MsgType == 'D') {
// New Order Single — inline at compile time
parse_new_order(raw, len);
} else if constexpr (MsgType == '8') {
// Execution Report
parse_execution(raw, len);
}
}
if constexpr 분기는 완전히 컴파일 타임에 해결됩니다 — 생성된 기계어 코드는 관련된 경로만 포함하며 분기 오버헤드가 전혀 없습니다. 이 기법은 링크 타임 최적화(LTO)와 결합되어, 프로토콜 파싱이 본질적으로 순차적인 메모리 읽기 나열로 풀려 있는 바이너리를 만들어냅니다.
현대 C++20/23의 consteval, 컨셉(concepts), 컴파일 타임 컨테이너 같은 기능은 이를 한층 더 밀어붙여, 전체 주문 검증 파이프라인을 컴파일 타임에 계산하는 것까지 가능하게 합니다.
캐시 친화적 설계와 커널 바이패스 네트워킹
서브마이크로초 지연 시간에서는 CPU 캐시 계층이 가장 중요한 최적화 대상이 됩니다. 메인 메모리로의 캐시 미스는 약 100나노초의 비용을 치릅니다 — 전체 지연 시간 예산이 500ns라면 이는 영원과 같은 시간입니다.
// BAD: Array of Pointers (AoP) — cache-hostile
std::vector<std::unique_ptr<Order>> orders; // Each access = pointer chase + cache miss
// GOOD: Struct of Arrays (SoA) — cache-friendly
struct OrderBook {
std::vector<double> prices; // Contiguous in memory
std::vector<uint32_t> quantities; // Contiguous in memory
std::vector<uint64_t> order_ids; // Contiguous in memory
};
// Iterating prices = sequential cache line reads = fast
네트워킹에서는 Linux 커널의 TCP/IP 스택이 패킷당 5~15마이크로초의 지연 시간을 추가합니다. 트레이딩 회사들은 이를 완전히 우회합니다:
- DPDK(Data Plane Development Kit) — 커널을 우회해 사용자 공간에서 NIC를 직접 폴링합니다. 서브마이크로초 패킷 처리를 달성합니다.
- Solarflare OpenOnload — 익숙한 소켓 API를 유지하는 커널 바이패스입니다. 주식과 선물 거래에서 광범위하게 사용됩니다.
- FPGA NIC — Xilinx Alveo 같은 카드는 하드웨어에서 시장 데이터를 파싱하고 주문을 생성할 수 있어 나노초 수준의 지연 시간을 달성합니다.
탈중앙화 거래소조차 이런 원칙의 혜택을 봅니다. GaiaEx 같은 플랫폼을 구동하는 Hyperliquid의 L1 체인은 고처리량, 저지연 합의를 염두에 두고 설계되었으며, 이에 연결하는 마켓 메이커들은 가격 업데이트를 받은 시점과 주문을 제출한 시점 사이의 시간을 최소화하기 위해 최적화된 C++ 클라이언트를 사용합니다.
거래소 매칭 엔진은 어떻게 만들어지는가
모든 거래소의 심장에는 매칭 엔진이 있습니다 — 매수와 매도 주문을 짝지어주는 구성요소입니다. 이는 모든 금융 소프트웨어 중 가장 지연 시간에 민감한 부분이며, 거의 항상 C++로 작성됩니다.
단순화한 매칭 엔진 아키텍처:
class MatchingEngine {
// One order book per instrument
std::unordered_map<Symbol, OrderBook> books_;
void on_new_order(const Order& order) {
auto& book = books_[order.symbol];
auto matches = book.match(order);
for (const auto& fill : matches) {
publish_execution(fill); // To trading firms
update_market_data(fill); // To data feed
}
if (order.remaining_qty > 0) {
book.insert(order); // Rest on the book
}
}
};
프로덕션 매칭 엔진은 이 골격을 훨씬 뛰어넘는 최적화를 합니다:
- 가격-시간 우선순위 — 같은 가격의 주문은 도착 순서대로 체결되며, 나노초 단위 타임스탬프로 추적됩니다.
- 사전 할당된 주문 풀 — 매칭 중 힙 할당이 없습니다. 주문은 고정된 풀에서 재활용됩니다.
- 락 없는 설계 — 각 상품의 오더북은 전용 코어에서 실행됩니다. 오더북 간 락이 필요 없습니다.
- 결정적 재생(Deterministic replay) — 모든 주문과 매칭은 규제 준수와 재해 복구를 위해 영구 저장소에 저널링됩니다.
이 수준의 시스템을 구축하는 것이 C++가 진정으로 빛나는 지점입니다. 다른 어떤 주류 언어도 메모리 레이아웃, 스레드 스케줄링, 캐시 동작, 네트워크 I/O를 동시에 통제할 수 있는 능력을 주지 않습니다 — 이 네 가지가 초저지연 엔지니어링의 네 기둥입니다. 다음 거래소를 만들든, 마켓 메이커로 거래소에 연결하든, 프랍 트레이딩 회사에서 실행을 최적화하든, C++는 여전히 의심할 여지 없는 왕입니다.

