4. 内存带宽测试工具:使用 stream、mbw、lmbench 等工具进行基准测试

好,咱们进入正题。内存带宽到底够不够用,不能靠猜,得拿数据说话。我个人习惯,遇到性能瓶颈时,第一件事就是跑一遍内存基准测试。这就像医生看病先量体温一样,基础但关键。

今天要聊的三个工具——stream、mbw、lmbench——是我这些年排查问题时的老搭档。它们各有侧重,配合起来能把内存子系统的底裤都看穿。

4.1 为什么需要专门的带宽测试工具?

你可能会问:我用 dd 拷贝一个大文件,或者跑个 memcpy 程序,不也能测带宽吗?

嗯,这里有个坑。普通拷贝测试往往只测了单向读或写,而且容易被 CPU 缓存干扰。真正的内存带宽瓶颈,往往出现在多核并发访问、读写混合、或者非连续地址访问的场景下。

我在项目中遇到过,一个数据库服务在 32 核机器上跑,总吞吐量死活上不去。用 dd 测带宽看起来正常,但一上 stream 就原形毕露——多核并发时,带宽直接腰斩。所以,专业工具才能暴露真实问题。

核心观点:内存带宽测试,要测的是「系统在极限压力下的可持续吞吐量」,而不是单线程、小数据量的理想值。

4.2 Stream:工业级带宽测试标杆

Stream 是啥?说白了,它就是内存带宽测试的「金标准」。几乎所有服务器厂商、芯片公司都在用。它的原理很简单:对一个大数组(远大于 CPU 缓存)执行四种基本操作,然后算带宽。

4.2.1 Stream 的四种测试模式

操作 含义 带宽计算方式
COPY 数组 A 拷贝到数组 B 2 × 数组大小 / 时间
SCALE 数组 A 乘以标量后写入 B 2 × 数组大小 / 时间
ADD 数组 A + 数组 B 写入 C 3 × 数组大小 / 时间
TRIAD 数组 A + 标量 × 数组 B 写入 C 3 × 数组大小 / 时间

你想想看,为什么 COPY 算 2 倍?因为读一次 A、写一次 B,总共两次内存访问。ADD 是读 A、读 B、写 C,三次。这个细节很重要——很多新手只看数值,不看计算方式,容易误判。

4.2.2 编译与运行

Stream 的编译有几个关键参数,我踩过坑,给你列出来:

# 下载源码
wget https://www.cs.virginia.edu/stream/FTP/Code/stream.c

# 编译(注意优化选项)
gcc -O3 -march=native -mtune=native -DSTREAM_ARRAY_SIZE=200000000 \
    -DNTIMES=20 stream.c -o stream

# 运行
./stream

我的经验:STREAM_ARRAY_SIZE 至少要设为最后一级缓存大小的 4 倍以上。比如 L3 缓存 32MB,数组大小就设 128MB 以上。太小了数据全在缓存里,测出来的是缓存带宽,不是内存带宽。

4.2.3 结果解读

跑完会看到类似这样的输出:

-------------------------------------------------------------
Function    Best Rate MB/s  Avg time     Min time     Max time
COPY:       42156.3         0.009543     0.009492     0.009621
SCALE:      41987.2         0.009578     0.009529     0.009654
ADD:        45231.1         0.013372     0.013261     0.013512
TRIAD:      45198.6         0.013376     0.013268     0.013514
-------------------------------------------------------------

怎么看?重点关注 TRIAD 的值,它最接近真实应用场景(读、写、计算混合)。然后对比你机器的理论带宽。比如 DDR4-3200 双通道,理论带宽约 51.2 GB/s,实测能到 42 GB/s 就算正常。如果只有 20 GB/s,那肯定有问题。

注意:我曾经遇到一台机器,TRIAD 带宽只有理论值的 30%。排查半天,发现是 BIOS 里把内存频率设成了 2133 MHz,而实际条子是 3200 的。所以跑 stream 之前,先确认内存频率和通道数对不对。

4.3 mbw:多线程带宽测试利器

Stream 默认是单线程的。但现实世界哪有单线程跑满带宽的好事?多核并发才是常态。这时候 mbw 就派上用场了。

mbw 全称 Memory BandWidth,它支持指定线程数,能模拟多核同时访问内存的场景。

4.3.1 安装与使用

# Ubuntu/Debian
apt install mbw

# CentOS/RHEL
yum install mbw

# 运行:指定内存大小(MB)和线程数
mbw -n 4 1024  # 4个线程,分配1024MB内存

4.3.2 输出解读

Long uses 8 bytes. Allocating 2*1073741824 elements = 17179869184 bytes of memory.
Using 4 threads to scan.
Bandwidth in MiB/s: 12345.6  (avg: 12345.6)
Bandwidth in MiB/s: 12345.6  (avg: 12345.6)
Bandwidth in MiB/s: 12345.6  (avg: 12345.6)
Bandwidth in MiB/s: 12345.6  (avg: 12345.6)

这里有个关键点:看单线程带宽和总带宽的比值。如果 4 线程的总带宽是单线程的 3.5 倍以上,说明内存控制器不错。如果只有 2 倍,说明有竞争瓶颈。

避坑指南:我曾经用 mbw 测一台双路服务器,发现 2 线程时带宽正常,4 线程反而下降。后来发现是 NUMA 节点间内存访问延迟太大,线程被调度到了不同 CPU 上,跨节点访问拖慢了速度。解决方案是用 taskset 绑定线程到同一个 NUMA 节点。

4.4 lmbench:微基准测试全家桶

lmbench 不是专门测带宽的,它是个微基准测试套件。但它的 lat_mem_rd 和 bw_mem 两个工具,对内存子系统分析特别有用。

4.4.1 延迟测试:lat_mem_rd

带宽和延迟是硬币的两面。有时候带宽够,但延迟高,应用照样卡。lat_mem_rd 能测出不同大小数据块的访问延迟:

# 安装
apt install lmbench

# 运行延迟测试:最大512MB,步长128字节,随机访问
lat_mem_rd 512 128

# 输出示例
0.00049 1.000
0.00098 1.000
...
8.00000 8.500    # 8MB时延迟8.5ns,说明还在L3缓存内
16.00000 12.300  # 16MB时延迟跳变,开始访问内存
...
512.00000 85.600 # 512MB时延迟85.6ns,纯内存延迟

怎么看?重点关注延迟从缓存跳到内存的那个拐点。如果拐点位置比你的缓存大小小很多,说明缓存命中率有问题。如果内存延迟超过 100ns,那得检查内存频率和时序了。

4.4.2 带宽测试:bw_mem

bw_mem 比 stream 更轻量,适合快速验证:

bw_mem 512M rd    # 读带宽
bw_mem 512M wr    # 写带宽
bw_mem 512M rdwr  # 读写混合带宽

我的习惯:先用 bw_mem 快速扫一遍,如果发现异常,再上 stream 做详细分析。lmbench 的好处是跑得快,30 秒出结果,适合做日常巡检。

4.5 三款工具对比总结

工具 核心用途 优势 不足
Stream 单线程极限带宽 工业标准,结果可对比 不支持多线程
mbw 多线程并发带宽 模拟真实多核场景 结果波动较大
lmbench 延迟 + 快速带宽 轻量,信息全面 精度不如 stream

4.6 我的测试流程建议

这些年我总结了一套流程,分享给你:

  1. 先确认硬件基线:用 dmidecode 看内存频率、通道数、容量。别上来就跑测试,先搞清楚硬件规格。
  2. 跑 lmbench 快速扫描:30 秒内确认延迟和带宽是否在合理范围。
  3. 跑 stream 做精确测量:记录 COPY、SCALE、ADD、TRIAD 四个值,和理论带宽对比。
  4. 跑 mbw 做压力测试:逐步增加线程数(1、2、4、8、16...),观察带宽扩展性。
  5. 交叉验证:如果 stream 和 mbw 结果矛盾,检查 NUMA 绑定、CPU 频率缩放、内存交错配置。

最后提醒一句:测试时一定要关掉 CPU 节能模式(比如 intel_pstate 设为 performance),否则频率波动会导致结果不稳定。我见过有人测了十次,结果从 30 GB/s 到 45 GB/s 乱跳,最后发现是节能模式在捣鬼。

好了,工具和方法都给你了。下一节咱们聊聊怎么分析这些测试结果,以及如何定位具体的瓶颈点。记住,工具只是手段,理解数据背后的含义才是关键。


想要更多资料,联系可可