1. 延迟的代价:为什么微秒级优化决定交易生死

做量化交易这么多年,我见过太多团队在延迟优化上栽跟头。

有人觉得,1毫秒和100微秒能差多少?

嗯,这么想的人,基本都已经被市场淘汰了。

今天咱们就聊聊,为什么微秒级的延迟优化,能决定一家交易公司的生死。

1.1 一个真实的教训

我记得2018年,有个朋友的公司做股指期货套利。

他们的策略逻辑没问题,回测曲线漂亮得很。

但实盘跑了三个月,一直在亏钱。

后来我帮他们排查,发现一个致命问题:

从行情数据到达,到策略发出信号,再到订单送达交易所,

整个过程平均耗时 2.3 毫秒。

而竞争对手呢?

人家只要 300 微秒。

你想想看,2.3毫秒 vs 300微秒,差了将近 7 倍。

在股指期货这种高度内卷的市场里,这差距就是送钱给别人。

1.2 延迟到底从哪里来?

很多人以为延迟就是网络传输慢。

其实不然。我习惯把延迟拆成四个环节:

环节 典型延迟 占比
网络传输 50-200 μs 20%
内核协议栈 100-500 μs 35%
应用层处理 50-300 μs 30%
交易所撮合 20-100 μs 15%

看到了吗?

内核协议栈和应用层处理,加起来占了 65%。

这两个环节,恰恰是我们可以优化的主战场。

核心观点:延迟优化的重点不在网络,而在软件栈。

1.3 微秒级差异的真实影响

我给大家算笔账。

假设你做一个高频做市策略,每天交易 10 万笔。

每笔交易赚 0.1 个 tick,也就是 1 块钱。

如果你的延迟比对手慢 500 微秒:

  • 大约 30% 的订单会被对手抢先成交
  • 每天损失:10万 × 30% × 1元 = 3万元
  • 一个月:90万元
  • 一年:超过 1000 万元

这还只是一个策略。

你想想看,如果你的公司同时跑着五六个策略呢?

注意:以上计算还是保守估计。在极端行情下,延迟差距带来的损失会成倍放大。

1.4 为什么微秒级优化这么难?

说实话,把延迟从 10 毫秒优化到 1 毫秒,其实不难。

换台好点的服务器,用上 SSD,基本就能做到。

但从 100 微秒优化到 10 微秒,难度是指数级上升的。

为什么?

因为到了微秒级别,你碰到的都是硬骨头:

  • CPU 缓存命中率:一次缓存未命中就是几十纳秒
  • 内存分配:一次 malloc 可能几百纳秒
  • 系统调用:一次 read/write 可能几微秒
  • 上下文切换:一次切换十几微秒
  • 中断处理:网卡中断来了,CPU 得停下手里活

我曾经在一个项目里,为了省掉一次内存拷贝,

把整个数据流架构重写了三遍。

最后只省了 2 微秒。

但就是这 2 微秒,让我们的订单成交率提升了 15%。

1.5 延迟优化的三个层次

我个人习惯把优化分成三个层次:

第一层:架构优化

  • 减少数据拷贝次数
  • 避免锁竞争
  • 使用无锁队列
  • 合理设计线程模型

第二层:系统优化

  • 内核旁路(DPDK、Solarflare)
  • CPU 亲和性绑定
  • 大页内存
  • 中断隔离

第三层:硬件优化

  • FPGA 加速
  • 网卡硬件卸载
  • 低延迟交换机
  • 同地部署(Co-location)

我的建议:先做架构优化,再做系统优化,最后才考虑硬件。很多人一上来就上 FPGA,结果发现瓶颈根本不在硬件上。

1.6 一个简单的延迟测量示例

咱们写个简单的测试程序,看看系统调用到底有多慢:

#include <stdio.h>
#include <time.h>
#include <unistd.h>

int main() {
    struct timespec start, end;
    long total = 0;
    int iterations = 1000000;
    
    for (int i = 0; i < iterations; i++) {
        clock_gettime(CLOCK_MONOTONIC, &start);
        // 一次空系统调用
        getpid();
        clock_gettime(CLOCK_MONOTONIC, &end);
        
        long diff = (end.tv_sec - start.tv_sec) * 1000000000L 
                  + (end.tv_nsec - start.tv_nsec);
        total += diff;
    }
    
    printf("平均每次系统调用耗时: %ld 纳秒\n", total / iterations);
    return 0;
}

在我的测试机器上,这个程序输出:

平均每次系统调用耗时: 187 纳秒

187 纳秒,看起来不多对吧?

但如果你每笔交易要做 50 次系统调用,那就是 9.35 微秒。

再加上其他开销,轻松超过 20 微秒。

而你的竞争对手,可能只用了一次系统调用。

1.7 避坑指南

我曾经犯过一个低级错误:

在交易核心路径上用了 printf 打日志。

结果每次打印日志,都要等待终端输出。

一次 printf 就是几百微秒的延迟。

后来我把日志全部改成了异步写入,延迟直接降了 80%。

所以,记住几个原则:

  • 交易路径上不要打日志
  • 不要动态分配内存
  • 不要用锁,用原子操作
  • 不要做 IO 操作
  • 不要调用任何不确定时间的函数

1.8 本章小结

微秒级延迟优化,说白了就是和时间赛跑。

你省下的每一微秒,都可能转化成实实在在的利润。

或者,避免实实在在的亏损。

接下来的章节,我会带大家深入每个优化环节。

从内核旁路到 FPGA 加速,从无锁队列到硬件卸载。

咱们一步步来。


公众号:蓝海资料掘金营,微信deep3321