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,延迟才降下来。
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 了。
我个人习惯,在选内存时,会先算这个公式。如果两个内存条算出来的延迟差不多,再去看带宽和价格。
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);
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 知识体系图
下面这张图,把内存选型的核心逻辑串起来了:
说白了,内存选型不是简单看参数。你得先搞清楚自己的业务场景:是延迟敏感还是吞吐敏感?是单机部署还是集群部署?然后一层层往下选。
我个人习惯,在搭建交易系统时,会先做一轮内存延迟测试。用 mlockall 锁住内存,再用 rdtsc 指令测量实际访问延迟。如果发现延迟抖动超过 1 微秒,那肯定有问题,得从大页、NUMA 绑定、甚至内存条本身去排查。
好了,内存选型这块就聊这么多。记住一句话:交易系统的内存,不是看谁跑得快,而是看谁跑得稳。 低延迟、低抖动、可预测,这才是王道。