4. 内存选型:DDR4 vs DDR5 vs HBM、内存时序(CL值)对延迟的影响、NUMA架构与内存亲和性

内存选型,在高频交易系统里,从来不是「容量够大就行」这么简单。

我见过太多团队,CPU选得顶级,网卡也上了Solarflare,结果内存延迟一测,硬生生把微秒级的优势给吃掉了。说白了,内存是数据从网卡到CPU的最后一公里,这公里跑得慢,前面全白费。

4.1 DDR4、DDR5、HBM:三选一,怎么选?

先看一张对比表,我直接给你干货:

参数 DDR4-3200 DDR5-5600 HBM2e
单条带宽 25.6 GB/s 44.8 GB/s ~460 GB/s(堆叠)
典型延迟(CL) CL14~CL16 CL36~CL40 CL值不直接可比,但整体延迟略高
物理位置 DIMM插槽 DIMM插槽 封装内/近封装
适用场景 通用低延迟 高带宽、大容量 GPU/FPGA紧耦合

我个人习惯这样判断:

  • DDR4:如果你的策略是「低延迟优先」,且数据量不大(比如订单簿快照),DDR4-3200 CL14 依然是性价比之王。我在项目中用DDR4跑过纳秒级时间戳队列,延迟非常稳。
  • DDR5:带宽翻倍,但CL值也翻倍了。说白了,延迟不一定比DDR4好。适合需要大吞吐的场景,比如同时处理多个市场的行情合并。
  • HBM:带宽恐怖,但延迟其实不占优。它更适合GPU或FPGA做大规模并行计算。如果你用CPU做交易,HBM基本用不上——除非你上的是Xeon Max系列那种带HBM的CPU。
核心结论: 高频交易里,延迟优先选DDR4,带宽优先选DDR5。别盲目追新。

4.2 内存时序(CL值)—— 一个被低估的杀手

CL值,全称CAS Latency。它代表内存从收到读命令到输出第一个数据,需要多少个时钟周期。

你想想看,DDR4-3200 CL14 和 DDR5-5600 CL40,哪个更快?

我们来算一笔账:

  • DDR4-3200:时钟周期 = 1 / (3200/2) = 0.625 ns。CL14 = 14 × 0.625 = 8.75 ns
  • DDR5-5600:时钟周期 = 1 / (5600/2) ≈ 0.357 ns。CL40 = 40 × 0.357 ≈ 14.28 ns

看到了吗?DDR5的绝对延迟反而更高。这就是为什么我常说:别只看频率,CL值才是延迟的命门

避坑指南: 我曾经在选型时,采购部门图便宜买了DDR5-4800 CL40的条子,结果延迟比旧机器DDR4-2666 CL16还差。后来我强制要求所有交易服务器必须用DDR4-3200 CL14或更低。记住,CL值每降低1,延迟减少约0.6~0.7ns,这在微秒级交易里就是天壤之别。

4.3 NUMA架构与内存亲和性——你的数据可能跑错路了

NUMA(Non-Uniform Memory Access),说白了就是:CPU有多个插槽,每个插槽有自己的本地内存。访问本地内存快,访问远端内存慢。

为什么会这样?因为跨插槽访问要走QPI/UPI总线,多跳几次,延迟直接翻倍。

我画了一张图,帮你理解这个结构:

CPU 0 (Socket 0) 核心 0~7 本地内存 (DDR4) 低延迟 CPU 1 (Socket 1) 核心 8~15 本地内存 (DDR4) 低延迟 UPI 总线 (高延迟) 远端访问 (慢!)

这张图里,CPU 0 访问自己的本地内存,延迟很低。但如果它去访问 CPU 1 的本地内存,就得走UPI总线,延迟直接翻倍甚至更多。

4.4 内存亲和性实战——代码怎么写?

在Linux下,我们用 numactl 来控制内存分配策略。我常用的做法是:

# 查看NUMA拓扑
numactl --hardware

# 绑定进程到CPU 0-3,并从节点0分配内存
numactl --cpunodebind=0 --membind=0 ./trading_engine

# 或者用interleave策略(不推荐高频场景)
numactl --interleave=all ./trading_engine

在代码里,也可以用 libnuma 做更精细的控制:

#include <numa.h>
#include <numaif.h>

void* allocate_on_node(size_t size, int node) {
    void* ptr;
    // 分配内存,并绑定到指定NUMA节点
    ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
               MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    struct bitmask *mask = numa_allocate_nodemask();
    numa_bitmask_setbit(mask, node);
    mbind(ptr, size, MPOL_BIND, mask->maskp, mask->size, 0);
    numa_free_nodemask(mask);
    return ptr;
}
我的经验: 在交易进程中,我会把行情处理线程和订单管理线程分别绑定到不同的NUMA节点上,并确保每个线程只访问自己节点的内存。这样能避免跨节点访问带来的延迟抖动。我曾经在一个4路服务器上,通过NUMA亲和性优化,把平均内存访问延迟从120ns降到了85ns。

4.5 总结一下内存选型的几个要点

  • 延迟敏感型:选DDR4-3200 CL14,配合NUMA绑定,延迟最低。
  • 带宽敏感型:选DDR5-5600,但要做好CL值变大的心理准备。
  • FPGA/GPU场景:考虑HBM,但别指望它比DDR4延迟低。
  • NUMA一定要绑:不绑的话,操作系统可能把你的数据放到远端内存,延迟直接翻倍。

嗯,内存这块就聊到这儿。记住一句话:高频交易里,内存不是用来存数据的,是用来减少延迟的


专注资料整理