20、系统性能优化:低延迟编程技巧

做订单流交易系统,说白了就是跟时间赛跑。别人快你1毫秒,可能就抢走了你碗里的肉。我这些年踩过的坑不少,今天把压箱底的经验掏出来聊聊。

20.1 低延迟编程:三种语言的选择

先说说语言选择。我个人习惯是:核心交易引擎用C++,辅助服务用Java,监控和脚本用Go。为什么这么分?

C++:极致性能的担当

C++是低延迟的王者。我曾在项目中用C++重写过一个Java的订单路由模块,延迟直接从50微秒降到了5微秒。关键技巧就几个:

  • 避免虚函数:虚函数调用会打断CPU流水线。我习惯用模板或if-constexpr替代
  • 内存池分配:new/delete太慢了,预分配一大块内存自己管理
  • 禁用异常:异常处理会生成大量隐藏代码,我一般用返回码
// 一个简单的内存池示例
template<typename T>
class ObjectPool {
    char* buffer;
    size_t index;
public:
    T* allocate() {
        return reinterpret_cast<T*>(buffer + (index++ * sizeof(T)));
    }
};

Java:GC是最大的敌人

Java做交易系统?可以,但得小心GC。我记得有一次线上系统突然卡顿,一查是Full GC停了200毫秒——这在交易里就是灾难。

  • 用堆外内存:DirectByteBuffer绕过GC
  • 对象池化:复用对象,减少垃圾产生
  • 使用Disruptor:无锁队列,比BlockingQueue快10倍

Go:并发简单但延迟不稳定

Go的goroutine很轻量,但调度器会带来不确定性。我一般用它做日志收集、监控告警这类非核心路径。注意:别在交易核心用Go,GC暂停虽然短,但不可预测。

我的建议:如果团队C++功底强,全栈C++最稳。如果招人难,核心C++ + 周边Java/Go是务实选择。

20.2 内存管理:别让内存成为瓶颈

内存访问延迟是CPU的几十倍。你想想看,一次L1缓存命中只要1纳秒,主存访问要100纳秒。优化内存就是优化性能。

缓存行对齐

CPU缓存是以64字节为单位的缓存行。如果两个线程修改同一个缓存行的不同变量,就会产生伪共享。我曾经因为这个,多线程性能反而比单线程还差。

// 避免伪共享:填充到64字节
struct alignas(64) PaddedCounter {
    volatile long value;
    char padding[64 - sizeof(long)];
};

数据局部性

遍历数组比遍历链表快得多,因为数组在内存里是连续的。我习惯把热点数据放在连续内存里,用数组而不是vector(vector可能重新分配)。

避坑指南:我曾经用std::map存订单簿,结果每次查找都要跳好多内存地址。换成跳表后,延迟降了40%。

20.3 缓存优化:把热数据留在CPU里

CPU缓存分L1/L2/L3,容量越来越小但速度越来越快。优化目标就是:让热数据尽量留在L1缓存里

缓存级别典型大小访问延迟
L132KB~1ns
L2256KB~4ns
L38-16MB~15ns
主存GB级~100ns

怎么做?减少数据大小。比如订单ID用int64_t而不是string,价格用定点数而不是double。我见过有人用string存股票代码,每次比较都要遍历字符串——改成uint64_t后,速度翻倍。

20.4 并行计算:多核的正确打开方式

现代CPU有几十个核,但并行不是简单开线程。我总结了几条原则:

  • 无锁数据结构:锁会阻塞线程,用CAS原子操作替代
  • 线程绑定CPU:避免线程在不同核间迁移,用pthread_setaffinity_np
  • 分而治之:把订单簿按股票代码分片,每个核处理一片
// 线程绑定到指定CPU核心
void bind_to_core(int core_id) {
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(core_id, &cpuset);
    pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
}
注意:并行不是万能药。如果任务本身很小,线程创建和同步的开销可能超过收益。我一般只在单核延迟超过10微秒时才考虑并行。

20.5 FPGA加速:硬件级的极致优化

FPGA是低延迟的终极武器。它用硬件逻辑直接处理数据,没有操作系统、没有中断、没有缓存缺失。延迟可以做到纳秒级

我参与过一个项目,用FPGA做行情解析和订单检查。结果:

  • 行情解析延迟从5微秒降到200纳秒
  • 订单风控检查从10微秒降到500纳秒
  • 整体吞吐量提升20倍

但FPGA也有代价:开发周期长、调试困难、成本高。我建议:先用软件验证逻辑,再迁移到FPGA。而且不是所有逻辑都适合FPGA——复杂的业务规则还是用CPU处理更灵活。

一句话总结:FPGA适合固定、重复、对延迟极度敏感的任务,比如行情解码、订单校验、简单的策略执行。

20.6 知识体系总览

下面这张图是我自己画的,把本章的核心知识点串起来了。你可以把它当作优化路线图:

订单流交易系统性能优化体系 性能优化目标 低延迟编程 C++: 模板/内存池/禁用异常 Java: 堆外内存/对象池/Disruptor Go: 非核心路径使用 内存管理 缓存行对齐(64字节) 避免伪共享 数据局部性优化 缓存优化 L1/L2/L3缓存层次 减小数据结构大小 定点数替代浮点数 并行计算 无锁数据结构(CAS) 线程绑定CPU核心 数据分片处理 FPGA加速 硬件级纳秒延迟 行情解析/订单校验 软件验证→硬件迁移 核心原则:从软件到硬件,逐层优化,量力而行

嗯,这张图基本把本章内容都涵盖了。从软件层的编程语言选择,到内存和缓存优化,再到并行计算和硬件加速,每个环节都有优化空间。但记住:不要一开始就上FPGA,先把软件层面的优化做到极致,再考虑硬件方案。

我的经验:80%的性能问题可以通过软件优化解决。FPGA是最后的手段,也是成本最高的手段。先做profiling,找到真正的瓶颈,再对症下药。

交易系统化学习资料 微信Strategy888888