- C++ 纳秒级交易系统设计
- 低延迟需求分析
- 核心数据结构 - Order Book
- 性能优化原则
- Lock-Free Data Structures
- 多核并发处理
- 性能测量
- C++ 特性在低延迟中的应用
- 相关技术栈
罗马帝国 Hueman Contracts
- 罗马人会在大型活动(如 Syracuse Maximus,可容纳 200,000-300,000 人)前一年,向农民、酿酒商等
预定物资 - 通过提前确定价格,发明了早期的衍生品交易
- 目的:
Reduce World Uncertainty
- 罗马人会在大型活动(如 Syracuse Maximus,可容纳 200,000-300,000 人)前一年,向农民、酿酒商等
World Uncertainty Index
- 该指数主要记录负面事件
- 人类作为心理性生物,对潜在损失的权重高于潜在收益
- 这解释了为什么人们难以安睡时,往往是担心
可能亏损
1.2 现代市场制造(Market Making)
- 定义:Market Maker 是在金融市场中提供流动性的角色,他们同时报出买入价(Bid)和卖出价(Ask)
- 盈利模式:通过
买卖价差(Bid-Ask Spread)获取微薄利润 - 特点:Market Making is aLosers Game
- 不是靠某个"银弹"打败市场
- 需要持续地在所有方面都做得好
- 小利润来自承担风险(optionality)的补偿
- 风险不可免费,因为价格会波动
1.3 C++ 纳秒级交易系统设计
为什么需要低延迟编程?
| 类别 | 说明 | 示例 |
|---|---|---|
| 快速响应不确定事件 | 对突发新闻、市场变化做出即时反应 | 新闻发布后更新报价、取消订单 |
| "聪明"地处理 | 包含优秀的模型、算法等智能决策 | 更好的交易策略、风险计算 |
第二部分:低延迟需求分析
2.1 高频交易中的延迟来源
在高频交易(HFT)系统中,延迟发生在多个环节:
消息到达 → 网络接收 → 协议解析 → 订单簿更新 → 策略计算 → 订单生成 → 发送确认 ↓ 每个环节都可能成为瓶颈2.2 延迟量级参考
| 延迟级别 | 典型耗时 | 说明 |
|---|---|---|
| 毫秒级 | 1-1000 ms | 传统系统 |
| 微秒级 | 1-1000 μs | 普通量化系统 |
| 纳秒级 | 1-1000 ns | 高频交易前沿 |
2.3 交易系统中的参与者与延迟要求
不同市场参与者对延迟的要求不同:
| 参与者类型 | 延迟敏感度 | 主要策略 |
|---|---|---|
| Market Maker | 极高 | 被动提供流动性,依赖快速响应 |
| Arbitrageur | 极高 | 跨市场/跨品种套利 |
| 统计套利 | 高 | 基于模型的交易 |
| 基本面交易 | 中低 | 基于长期分析 |
第三部分:核心数据结构 - Order Book
订单簿是交易系统的核心数据结构,记录市场中所有的买卖订单:
- Bid 侧:买家愿意购买的价格和数量
- Ask 侧:卖家愿意出售的价格和数量
- spread:
买卖价差 = Ask - Bid
3.2 操作
| 操作 | 说明 |
|---|---|
| Add/Insert | 添加新订单到订单簿 |
| Remove/Cancel | 取消已有订单 |
| Modify | 修改订单价格或数量 |
| Match | 价格匹配时成交 |
3.3 数据结构选择原则
高频交易系统选择订单簿数据结构时的考量:
- 查找速度:快速定位特定价格或订单
- 插入/删除效率:订单更新频繁
- 内存局部性:缓存友好
- 确定性延迟:避免最坏情况导致的延迟尖刺
第四部分:性能优化原则
4.1 核心工程原则
Make It Work, Make It Right, Make It Fast
- 先正确实现,再优化性能
Measure Before Optimizing
- 必须有
性能数据支撑优化决策
- 必须有
Optimize the Common Case
- 关注最频繁的执行路径
Embrace Hardware
- 充分利用 CPU 硬件特性
4.2 CPU 硬件特性利用
4.2.1 CPU 缓存(Cache)
| 缓存层级 | 典型大小 | 访问延迟 |
|---|---|---|
| L1 | ~32 KB | ~1 ns |
| L2 | ~256 KB | ~3-4 ns |
| L3 | ~10-30 MB | ~10-20 ns |
| 主存 | GB 级别 | ~100 ns |
优化策略:
数据对齐:确保数据结构与缓存行边界对齐Prefetch:提前将数据加载到缓存- 数据布局优化:使用
SoA(Structure of Arrays)替代AoS(Array of Structures)
4.2.2 分支预测(Branch Prediction)
- 现代 CPU 通过猜测分支方向来流水线执行
- 错误预测代价:15-25 个时钟周期
- 优化方法:
- 减少条件分支数量
- 使用 likely/unlikely 提示
- 考虑条件执行或查找表
4.2.3 指令级并行(ILP)
- CPU 可以在同一时钟周期执行多条独立指令
- 方法:
- 重排指令以增加并行度
- 减少数据依赖
- 展开循环
4.3 内存访问优化
4.3.1 NUMA(Non-Uniform Memory Access)
在多插槽服务器上:
- 每个 CPU 插槽有本地内存
- 访问远程内存有额外延迟
- 优化:线程优先访问本地内存
4.3.2 内存分配策略
| 策略 | 优点 | 缺点 |
|---|---|---|
| 预分配池 | 无分配延迟、缓存友好 | 内存浪费 |
| 对象池 | 减少碎片 | 管理复杂 |
| TLSF 等分配器 | 确定性分配时间 | - |
4.4 缓存优化技巧
数据结构设计
AoS vs SoA:
// AoS (Array of Structures) - 缓存不友好structOrder{uint64_tprice;uint32_tquantity;uint32_ttimestamp;};Order orders[1000000];// SoA (Structure of Arrays) - 缓存友好structOrderBook{uint64_tprices[1000000];uint32_tquantities[1000000];uint32_ttimestamps[1000000];};向量化(Vectorization)
- 利用 SIMD(Single Instruction Multiple Data)指令
- SSE/AVX 可以在单条指令处理多个数据
- 示例场景:批量价格计算、订单匹配
// 伪代码示例:批量处理 4 个价格__m256d prices=_mm256_load_pd(price_ptr);__m256d result=_mm256_mul_pd(prices,multiplier);_mm256_store_pd(result_ptr,result);第五部分:Lock-Free Data Structures
5.1 为什么需要无锁结构?
| 对比项 | 有锁方案 | 无锁方案 |
|---|---|---|
| 延迟 | 可能阻塞等待 | 确定性等待 |
| 优先级反转 | 可能发生 | 避免 |
| 死锁 | 可能发生 | 避免 |
| 复杂度 | 相对简单 | 实现复杂 |
5.2 原子操作
| 操作 | 说明 | CAS 用法 |
|---|---|---|
| CAS | Compare-And-Swap,比较并交换 | 最基础的无锁原语 |
| FAA | Fetch-And-Add,原子加法 | 计数器、序列号 |
| Load/Store | 原子读写 | 数据一致性 |
5.3 无锁订单簿设计考量
// 简化的无锁订单指针更新示例structalignas(64)OrderNode{uint64_tprice;uint32_tquantity;std::atomic<OrderNode*>next;};// 使用 CAS 更新链表boolupdate_head(std::atomic<OrderNode*>&head,OrderNode*new_node){OrderNode*old_head=head.load(std::memory_order_relaxed);new_node->next.store(old_head,std::memory_order_relaxed);returnhead.compare_exchange_weak(old_head,new_node,std::memory_order_release,std::memory_order_relaxed);}5.4 ABA 问题与解决方案
- ABA 问题:线程 A 读取到值 A,切换;线程 B 将值改为 C 再改回 A;线程 A 继续认为值没变
- 解决方案:
- 使用双宽 CAS(DCAS)存储指针+版本号
- 使用带计数的指针(如
std::shared_ptr) - 使用内存管理器的回收延迟
第六部分:多核并发处理
6.1 线程模型设计
高频交易系统典型线程模型:
| 模型 | 特点 | 适用场景 |
|---|---|---|
| 单线程事件循环 | 简单、无锁 | 单核心处理、IO 密集 |
| Per-Symbol 线程 | 每个品种独立处理 | 多品种、低耦合 |
| 管道模型 | 阶段间流水线 | 复杂处理流程 |
| 混合模型 | 结合多种模型 | 实际生产系统 |
6.2 避免 False Sharing
False Sharing发生在不同线程修改同一缓存行中的数据时:
// 错误示例:False Sharingstruct{std::atomic<uint64_t>bid_price;// 线程 1 修改std::atomic<uint64_t>ask_price;// 线程 2 修改// 两者在同一缓存行,导致伪共享}order_book;// 正确示例:填充避免 False Sharingstruct{std::atomic<uint64_t>bid_price;// 线程 1 修改charpadding[64-sizeof(uint64_t)];std::atomic<uint64_t>ask_price;// 线程 2 修改charpadding2[64-sizeof(uint64_t)];}order_book;6.3 内存屏障(Memory Barriers)
| 屏障类型 | 说明 | 使用场景 |
|---|---|---|
| Compiler Barrier | 防止编译器重排 | 汇编内联 |
| Hardware Barrier | 防止 CPU 重排 | 多线程同步 |
| Full Barrier | 双向阻塞 | 重要同步点 |
| Acquire | 获取之前的加载 | 读取共享数据 |
| Release | 释放之后的存储 | 写入共享数据 |
第七部分:性能测量方法
7.1 测量的重要性
“If you can’t measure it, you can’t improve it.”
性能优化的前提是准确的测量。
7.2 测量工具与技术
7.2.1 硬件性能计数器(PMCs)
使用perf工具:
# 统计缓存命中率perfstat-ecache-references,cache-misses ./trading_system# 记录并分析perf record-g./trading_system perf report7.2.2 低延迟测量技术
| 技术 | 说明 | 精度 |
|---|---|---|
| RDTSC | Read Time-Stamp Counter,读取 CPU 时间戳计数器 | 纳秒级 |
| RDTSCP | RDTSC with Processor ID,防止乱序 | 纳秒级 |
| Clock_gettime | POSIX 时钟 | 纳秒级(CLOCK_MONOTONIC) |
// 使用 RDTSC 进行延迟测量inlineuint64_tget_time_ns(){unsignedintdummy;return__rdtscp(&dummy);}// 使用示例uint64_tstart=__rdtsc();// ... 操作 ...uint64_tend=__rdtsc();uint64_tcycles=end-start;doublelatency_ns=cycles/(CPU_FREQ_GHZ*1000);7.2.3 追踪与日志
- 追踪(Tracing):记录每个操作的时序
- 采样分析:低开销的 CPU 使用分析
- 热路径分析:识别最频繁的执行路径
7.3 延迟分布分析
| 指标 | 说明 | 重要性 |
|---|---|---|
| Mean | 平均延迟 | 整体性能 |
| Median (P50) | 中位数 | 典型情况 |
| P99 | 99 百分位 | 大多数请求 |
| P999 | 99.9 百分位 | 尾部延迟 |
| Max | 最大延迟 | 极端情况 |
高频交易更关注 P999 而非平均值,因为一次极端延迟可能导致错过交易机会。
7.4 微基准测试(Microbenchmarks)
使用 Google Benchmark 库:
#include<benchmark/benchmark.h>staticvoidBM_OrderBookUpdate(benchmark::State&state){OrderBook book;for(auto_:state){book.update(100,500);}}BENCHMARK(BM_OrderBookUpdate);BENCHMARK_MAIN();第八部分:C++ 语言特性在低延迟中的应用
8.1 编译期计算
| 技术 | 说明 | 示例 |
|---|---|---|
| constexpr | 编译期求值 | 常量计算 |
| 模板元编程 | 类型级计算 | 类型特化 |
| if constexpr | 编译期条件分支 | 减少分支 |
8.2 虚函数与内联
| 类型 | 优点 | 缺点 |
|---|---|---|
| 普通虚函数 | 运行时多态 | 间接调用、难以内联 |
| 模板 | 编译期多态 | 代码膨胀 |
| CRTP | 静多态、可内联 | 语法复杂 |
// CRTP 示例template<typenameDerived>classOrderBookBase{public:voidupdate(){// 静态派发,编译时内联static_cast<Derived*>(this)->update_impl();}};classFastOrderBook:publicOrderBookBase<FastOrderBook>{public:voidupdate_impl(){/* 高效实现 */}};8.3 内存管理
| 技术 | 开销 | 适用场景 |
|---|---|---|
| 栈分配 | 无 | 小对象、固定大小 |
| 对象池 | 最小 | 频繁创建销毁 |
| arena/区域分配 | 低 | 同生命周期对象 |
| 智能指针 | 有一定开销 | 非性能关键路径 |
第九部分:建议
9.1 设计决策清单
在低延迟系统设计中需要考虑的问题:
- 数据结构的时间复杂度是否满足需求?
- 是否有不必要的锁竞争?
- 缓存命中率如何?
- 是否有 False Sharing?
- 分支预测成功率如何?
- 内存分配是否可预期?
- 系统调用的频率?
原则
- 强调工程实践,关注实际问题
- 持续改进,不是一次性的银弹方案
- 全面优秀:Market Making 的核心理念——
持续在所有方面做得好 - 未雨绸缪:像罗马人一样
做好规划
技术栈
| 类别 | 工具/库 | 用途 |
|---|---|---|
| 性能分析 | perf, VTune, Valgrind | 性能分析 |
| 基准测试 | Google Benchmark | 微基准测试 |
| 并发 | TBB, Folly, Seastar | 并行库 |
| 网络 | DPDK, Solarflare | 低延迟网络 |
| 日志 | spdlog, libfmt | 高性能日志 |
- 书籍:
- Advances in Financial Machine Learning- López de Prado
- Quantitative Trading- Chan
- C++ High Performance- Bjorn Andrist
- 会议:CppCon, Meeting C++, ACCU
- 论文:ACM SIGOPS, IEEE Transactions
9.2 反模式(Anti-Patterns)
| 反模式 | 问题 | 替代方案 |
|---|---|---|
| 全局锁 | 严重串行化 | 无锁结构或细粒度锁 |
| 不必要的虚函数 | 间接调用 | CRTP、模板 |
| 过度抽象 | 代码复杂度 | YAGNI 原则 |
| 忽略测量 | 盲目优化 | 先测量再优化 |
| premature optimization | 浪费时间 | 关注瓶颈 |
总结
- 市场制造的本质:需要持续在所有方面做到优秀
- 低延迟的两大驱动:快速响应 + 智能决策
- 优化方向:
- CPU 缓存友好设计
- 无锁数据结构
- 多核并发优化
- 准确的性能测量
- 工程原则:测量优先、关注瓶颈、持续改进
高频交易系统的优化是一个系统性工程,需要在算法、数据结构、系统设计等多个层面协同优化。