GaiaEx AcademyGaiaEx Academy
面向低延迟交易系统的 C++
开发者编程13 min read

面向低延迟交易系统的 C++

为什么最快的交易公司一切都用 C++ 来写

分享文章

为什么 C++ 是速度之王

在对延迟敏感的技术栈里,C++ 依然牢牢占据着撮合路径:Globex 这类交易场所、许多股票行情源,以及加密引擎(包括 Hyperliquid 级别的基础设施),都会编译成可预测的机器码,中间不会藏着一次 GC(垃圾回收)停顿。

是什么让 C++ 在低延迟交易中独具优势?

  • 没有垃圾回收器——没有不可预测的停顿。你能精确控制内存何时分配、何时释放。
  • 贴近硬件——可直接访问 CPU 缓存行、SIMD 指令、内存映射 I/O,以及内核旁路(kernel bypass)网络。
  • 编译期计算——模板元编程把工作从运行时挪到编译期,生成的代码快得堪比手写汇编。
  • 可预测的延迟——只要编码足够讲究,你就能做到亚微秒级的 tick-to-trade(行情到下单)延迟,且抖动极小。

现实检验:在最快的交易席位上,tick-to-trade 的延迟预算往往是亚微秒级的。一次意外的内存分配或一次缓存未命中,就可能吃掉整个预算——所以团队会像守护生产流水线一样守护热路径(hot path)。

Why the hot path stays in native code Deterministic • No GC safepoints on path • Explicit memory & layout • SIMD / cache control • Kernel bypass friendly Measured in ns/µs • p99 > p50 matters • Jitter kills co-location edge • Replayable binaries • Fixed pools / arenas Interop • NIC / FPGA vendors ship C/C++ • DPDK, kernel modules • FIX / binary feeds • Same ABI as OS
交易所技术栈看重的是可预测的延迟和与硬件的紧密耦合——对于这种岗位要求,C++ 是默认工具。

内存管理:栈、堆与自定义分配器

在低延迟 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
Stack vs heap on the hot path Stack / thread-local OrderUpdate on stack — bounded, LIFO, cache-hot Pool / arena — O(1) reuse, no malloc churn Heap (generic) new/malloc — allocator locks, fragmentation Unpredictable latency — avoid per tick
把结构体放在栈上或预热好的池里;在 tick 处理器上,把命中堆当成 bug 来对待。

无锁数据结构与并发

互斥锁(mutex)是低延迟代码的敌人。即便没有竞争,一次 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 这类大小,取模就能变成一次按位与运算。

典型的接线方式:NIC(网卡)线程入队,策略线程出队——只要拓扑设计得当,整条传输路径上就没有互斥锁。

SPSC ring buffer (one writer, one reader) Producer feed / I/O thread Power-of-two slots • acquire/release atomics Consumer strategy thread alignas(64) head/tail — separate cache lines to kill false sharing Memory order: relaxed on local index, acquire/release across handoff
只有一个生产者和一个消费者,就能跳过互斥锁;正确的对齐能让各核心不去争抢同一个缓存行。

模板:编译期计算

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 网卡——Xilinx Alveo 及同类板卡能在硬件中解析行情并生成订单,实现纳秒级的延迟。

就连去中心化交易所也能从这些原则中受益。Hyperliquid 的 L1 链——为 GaiaEx 这类平台提供动力——在设计之初就考虑了高吞吐、低延迟的共识,连接它的做市商会使用经过优化的 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
        }
    }
};

生产级撮合引擎的优化远不止这副骨架:

  • 价格-时间优先(Price-time priority)——同一价格上的订单按到达先后成交,用纳秒级时间戳来追踪。
  • 预分配的订单池——撮合过程中不做堆分配。订单从固定的池里循环复用。
  • 无锁设计——每个品种的订单簿跑在专属的核心上。无需跨订单簿加锁。
  • 可确定性重放(Deterministic replay)——每一笔订单和每一次撮合都会记入持久化存储,以满足监管合规和灾难恢复的需要。

构建这种级别的系统,正是 C++ 真正大放异彩的地方。没有任何其他主流语言能同时给你对内存布局、线程调度、缓存行为和网络 I/O 的掌控——这正是超低延迟工程的四大支柱。无论你是在打造下一家交易所、作为做市商连接到某家交易所,还是在自营公司里优化执行,C++ 仍是无可争议的王者。