3. 内存选型:DDR4 vs DDR5、内存频率与时序、大页内存与内存绑定技术

内存这块,说实话,是交易系统里最容易被人忽视,但也是最容易出幺蛾子的环节。CPU 算力不够可以堆核,网络延迟高可以换卡,但内存要是选错了,整个系统的延迟曲线会变得极其难看。我见过太多团队,CPU 和网卡都顶配,结果跑高频交易策略时,时不时卡一下,最后定位到是内存带宽瓶颈。

今天咱们就把内存选型这件事聊透。从 DDR4 和 DDR5 怎么选,到频率时序的坑,再到内核态的大页和绑定技术,一个一个过。

3.1 DDR4 vs DDR5:不是越新越好

很多人觉得,DDR5 是新一代,肯定比 DDR4 强。嗯,这个想法在消费级市场可能没错,但在交易系统里,真不一定。

我直接说结论:对于延迟敏感的交易系统,DDR4 在某些场景下反而更优。

为什么会这样?咱们看几个关键指标:

指标 DDR4-3200 DDR5-4800 说明
单条容量 最大 64GB 最大 256GB DDR5 大容量优势明显
带宽 ~25.6 GB/s ~38.4 GB/s DDR5 带宽更高
CAS 延迟 (CL) CL14 ~ CL16 CL36 ~ CL40 DDR4 延迟更低
实际访问延迟 ~10ns ~14ns DDR4 胜出
价格 便宜 较贵 DDR4 性价比高

你看,DDR5 的带宽确实高,但它的 CAS 延迟也高了不少。对于交易系统这种对单次内存访问延迟极其敏感的场景,DDR4 的低延迟特性反而更吃香。

我在项目中遇到过一件事:某团队升级了 DDR5 平台,结果订单处理延迟反而增加了 2-3 微秒。排查了半天,发现是内存访问延迟变高了。后来他们换回了高频 DDR4,延迟才降下来。

避坑指南: 如果你做的是高频交易(HFT),对单次内存访问延迟要求极高,优先考虑 DDR4-3200 CL14 或 DDR4-3600 CL16。如果你做的是量化策略回测,需要大容量内存来加载海量历史数据,DDR5 的大容量优势就体现出来了。

3.2 内存频率与时序:别只看频率

很多人选内存,只看频率。3200MHz、3600MHz、4800MHz,觉得频率越高越快。其实不然。

内存的实际访问延迟,是由频率和时序共同决定的。计算公式很简单:

实际延迟 (ns) = (CL × 2000) / 内存频率 (MHz)

举个例子:

  • DDR4-3200 CL14:延迟 = (14 × 2000) / 3200 = 8.75ns
  • DDR4-3600 CL18:延迟 = (18 × 2000) / 3600 = 10.0ns
  • DDR5-4800 CL40:延迟 = (40 × 2000) / 4800 = 16.67ns

看到了吧?DDR4-3200 CL14 的实际延迟,比 DDR4-3600 CL18 还要低。更不用说 DDR5 了。

我个人习惯,在选内存时,会先算这个公式。如果两个内存条算出来的延迟差不多,再去看带宽和价格。

我的经验: 对于交易系统,我一般推荐 DDR4-3200 CL14 或 DDR4-3600 CL16。这两个组合的延迟都在 9-10ns 之间,性价比最高。如果预算充足,可以上 DDR4-3600 CL14,延迟能做到 7.8ns 左右,但价格会翻倍。

3.3 大页内存:减少 TLB Miss 的利器

嗯,这里要聊一个比较底层的东西——大页内存。说白了,就是让操作系统用更大的内存页来管理内存。

默认情况下,Linux 使用 4KB 的小页。如果你的交易程序需要访问大量内存(比如几十 GB 的订单簿数据),TLB(页表缓存)会频繁失效,导致每次内存访问都要查页表,延迟飙升。

我举个例子你就明白了:

  • 4KB 小页:要管理 64GB 内存,需要 1600 万个页表项。TLB 根本装不下。
  • 2MB 大页:同样 64GB 内存,只需要 32000 个页表项。TLB 命中率大幅提升。
  • 1GB 巨页:只需要 64 个页表项。TLB 几乎不会 miss。

配置大页内存的步骤很简单:

# 查看当前大页配置
cat /proc/meminfo | grep Huge

# 配置 1024 个 2MB 大页(总共 2GB)
echo 1024 > /proc/sys/vm/nr_hugepages

# 或者使用 sysctl 永久配置
echo "vm.nr_hugepages=1024" >> /etc/sysctl.conf
sysctl -p

程序里使用大页也很方便,以 C++ 为例:

#include <sys/mman.h>

// 分配 2MB 对齐的大页内存
void* ptr = mmap(NULL, 2 * 1024 * 1024,
                 PROT_READ | PROT_WRITE,
                 MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
                 -1, 0);

if (ptr == MAP_FAILED) {
    perror("mmap failed");
    exit(1);
}

// 使用完后释放
munmap(ptr, 2 * 1024 * 1024);
重要: 我曾经在一个订单撮合引擎里,把内存分配从默认 4KB 页改成 2MB 大页,结果延迟抖动从 50 微秒降到了 5 微秒以内。效果立竿见影。

3.4 内存绑定:把数据固定在某个 NUMA 节点

现代服务器都是 NUMA(非统一内存访问)架构。说白了,就是 CPU 访问本地内存快,访问远端内存慢。

如果你不绑定内存,操作系统可能会把你的数据分配到任意 NUMA 节点上。结果就是,CPU 访问数据时,可能要走 QPI/UPI 总线去远端拿,延迟增加 30%-50%。

我建议的做法是:把交易程序绑定到某个 NUMA 节点,同时把内存也分配在这个节点上。

numactl 工具可以轻松实现:

# 查看 NUMA 拓扑
numactl --hardware

# 把程序绑定到 NUMA 节点 0,内存也分配在节点 0
numactl --cpunodebind=0 --membind=0 ./trade_engine

# 或者使用 --localalloc,让内存尽量分配在本地节点
numactl --cpunodebind=0 --localalloc ./trade_engine

在代码里,也可以用 mbind 系统调用来精确控制:

#include <numaif.h>
#include <stdlib.h>

void* allocate_on_node(size_t size, int node) {
    void* ptr = malloc(size);
    if (!ptr) return NULL;

    // 把这块内存绑定到指定 NUMA 节点
    unsigned long nodemask = 1UL << node;
    if (mbind(ptr, size, MPOL_BIND,
              &nodemask, sizeof(nodemask) * 8,
              MPOL_MF_STRICT) != 0) {
        perror("mbind failed");
        free(ptr);
        return NULL;
    }
    return ptr;
}
注意: 内存绑定和大页内存可以同时使用。我一般先配置大页,再用 numactl 把程序绑定到指定 NUMA 节点。这样既能减少 TLB miss,又能避免跨 NUMA 访问。

3.5 知识体系图

下面这张图,把内存选型的核心逻辑串起来了:

内存选型核心逻辑 DDR4 vs DDR5 选型 频率与时序平衡 大页内存 (HugePages) NUMA 内存绑定 低延迟 + 高吞吐 + 稳定可预测 延迟优先 → DDR4 容量优先 → DDR5 实际延迟 = CL × 2000 / 频率 减少 TLB Miss 避免跨 NUMA 访问

说白了,内存选型不是简单看参数。你得先搞清楚自己的业务场景:是延迟敏感还是吞吐敏感?是单机部署还是集群部署?然后一层层往下选。

我个人习惯,在搭建交易系统时,会先做一轮内存延迟测试。用 mlockall 锁住内存,再用 rdtsc 指令测量实际访问延迟。如果发现延迟抖动超过 1 微秒,那肯定有问题,得从大页、NUMA 绑定、甚至内存条本身去排查。

一个小技巧: 如果你用的是 Intel 的 CPU,可以开启 Sub-NUMA Clustering(SNC)功能。它能把一个 NUMA 节点再拆成两个,进一步降低内存访问延迟。不过这个功能需要 BIOS 支持,而且对操作系统版本有要求。

好了,内存选型这块就聊这么多。记住一句话:交易系统的内存,不是看谁跑得快,而是看谁跑得稳。 低延迟、低抖动、可预测,这才是王道。

专注资料整理