简介:lmbench-3.0 是一款由 Larry McVoy 编写的多平台开源性能基准测试工具,面向系统管理员、内核与驱动开发者以及硬件评测人员,用于评估系统综合性能,尤其在内存带宽与内存延时测试方面表现突出。资源包共 225 个文件,约 508KB,以 63 个 C 源码文件为核心,配合 10 个头文件、5 个 Makefile 及 configure 等构建脚本,另有 36 个 .8、16 个 .tbl、6 个 .ms 等手册与文档文件,以及 results、stats、getsummary 等结果统计脚本,结构完整、便于二次编译与定制测试。目前已有 3658 人学习下载。借助该工具,读者可自行编译运行,通过内存拷贝、填充等模式测量最大吞吐量,并量化读取、写入与查找操作的响应延迟,还可调整数据块大小、迭代次数并开展多线程并行评估,从而定位性能瓶颈、对比不同算法或硬件配置的实际表现。
1. 从一次内存带宽对不上说起:lmbench-3.0 到底测什么
前阵子帮朋友排查一台二手服务器,他咬定内存条有问题:跑某款流行跑分软件,内存带宽只有标称值的一半。我让他换 lmbench-3.0 重测,结果带宽直接翻倍,延时也落在合理区间。问题不在内存,而在那款跑分软件的多线程调度和缓存策略把结果带偏了。这件事让我再次确认,测内存这件事,工具选错,后面全是玄学。
lmbench-3.0 是一套经典的微基准测试工具集,核心能力就两块:内存延时测试和内存带宽测试。它不追求跑出一个好看的分数,而是把内存子系统的真实行为拆开给你看——顺序读、顺序写、随机访问、不同 stride 下的表现,以及跨 CPU 访问远端内存的代价。适合谁?做服务器选型、调优 JVM/数据库、验证 NUMA 配置、排查内存瓶颈的工程师。它老,但老得可靠,很多云厂商内部做机型对比时,lmbench 依然是基准之一。
2. 把 lmbench-3.0 跑起来:编译、目录结构与最小验证
2.1 获取源码与编译前的依赖检查
lmbench-3.0 的源码包结构很朴素,解压后进入lmbench-3.0目录,先别急着 make。它依赖一套标准的 C 编译工具链,以及gcc、make、libtirpc-dev(部分新系统上 RPC 头文件被拆出去了)。我一般先跑一遍依赖确认:
# 检查编译器和 make 是否就位 gcc --version make --version # Debian/Ubuntu 系补 RPC 头文件,否则 results 相关组件可能编译失败 sudo apt-get install -y libtirpc-dev # 进入源码目录 cd lmbench-3.0逻辑说明:lmbench 的make会先编译src/下的各个 benchmark 二进制,再生成bin/目录下的可执行文件。如果系统缺少 RPC 头,lat_rpc、bw_rpc这类网络相关测试会编译不过,但内存测试本身不依赖它们。参数上,libtirpc-dev只是补齐头文件,不影响内存测试的编译产物。
2.2 一次完整的编译与目录确认
# 标准编译,默认优化等级由 Makefile 控制 make # 编译完成后确认关键二进制存在 ls -l bin/x86_64-linux-gnu/你会看到lat_mem_rd、bw_mem、lat_ctx等文件。lat_mem_rd负责内存延时测试,bw_mem负责内存带宽测试,这两个是本文重点。bin/下的目录名随架构变化,常见是x86_64-linux-gnu,如果是 ARM 机器则是aarch64-linux-gnu,脚本里最好用uname -m动态拼路径,别写死。
提示:如果
make报错找不到rpc/rpc.h,先装libtirpc-dev再重跑;如果只是内存测试,也可以在src/Makefile里把 RPC 相关目标注释掉,但我不建议,保持完整编译后续扩展更方便。
2.3 最小验证:先跑一个延时测试确认环境正常
# 进入 bin 目录,用 64MB 工作集、stride 128 字节做一次快速延时测试 cd bin/x86_64-linux-gnu ./lat_mem_rd -t 64 128逻辑说明:-t指定总工作集大小(单位 MB),这里 64MB 是为了超出 LLC 容量,逼出真实内存访问;128是 stride,单位字节,表示每次跳跃 128 字节访问一次。输出是一张「工作集大小 vs 延时(纳秒)」的表。如果 64MB 处的延时明显高于 4MB 处,说明缓存层级在起作用,环境正常。参数上,工作集要大于目标 CPU 的 LLC 容量,否则测的是缓存不是内存;stride 一般取 64 或 128,对应 cache line 大小。
3. 内存延时测试:lat_mem_rd 的参数怎么设、结果怎么看
3.1 lat_mem_rd 的工作原理与 stride 选择
lat_mem_rd的核心是一个指针追逐(pointer chasing)循环:它在一块连续内存里按固定 stride 建立链表,然后沿着链表跳,每次跳的地址依赖上一次的结果,从而阻止 CPU 乱序执行和预取器提前把数据拉进缓存。这样测出来的才是真正的内存访问延时,而不是缓存命中延时。
stride 的选择直接决定你测的是哪一层。stride 等于 cache line 大小(通常 64 字节)时,每次访问都落在新的 cache line 上,测的是最坏情况下的内存延时;stride 小于 cache line 时,可能多次访问落在同一行,测出来偏乐观。我一般会跑两组:stride 64 和 stride 128,对比看预取器的影响。
# 工作集从 512KB 到 512MB,stride 64,覆盖 L1/L2/L3/内存四个层级 ./lat_mem_rd -t 512 64 # 同样范围,stride 128,观察 stride 翻倍后延时曲线的变化 ./lat_mem_rd -t 512 128逻辑说明:-t 512表示最大工作集 512MB,lmbench 会自动从较小的工作集开始逐步增大,输出一条完整的延时曲线。参数上,工作集上限建议设为 LLC 容量的 4 到 8 倍,太小看不到内存平台,太大跑得慢且容易触发 swap。stride 64 和 128 的对比能帮你判断硬件预取器是否在 stride 128 时仍然有效。
3.2 读懂延时曲线:平台、拐点与 NUMA 信号
输出表格里,第一列是工作集大小,第二列是延时(纳秒)。你会看到几个明显的平台:L1 命中约 1 纳秒,L2 约 3 到 5 纳秒,L3 约 10 到 20 纳秒,到了内存约 60 到 100 纳秒(视平台而定)。拐点位置对应各级缓存容量,如果拐点位置和 CPU 标称的缓存大小对不上,可能是 stride 设置不当或系统有其它负载干扰。
NUMA 机器上还有一个信号:如果工作集超过单节点内存容量,延时曲线会再上一个台阶,因为部分访问要跨节点。这时候可以配合numactl绑定节点再测一次,对比本地和远端访问的差值。常见做法是:
# 绑定到 node 0 的 CPU 和内存,测本地访问延时 numactl --cpunodebind=0 --membind=0 ./lat_mem_rd -t 512 64 # 绑定 CPU 到 node 0,但内存只从 node 1 分配,测远端访问延时 numactl --cpunodebind=0 --membind=1 ./lat_mem_rd -t 512 64逻辑说明:--cpunodebind限定 CPU 在哪个节点执行,--membind限定内存从哪个节点分配。两次结果的差值就是跨节点访问的额外代价。参数上,如果机器有多个节点,建议每个节点都跑一遍,取最差和最好值作为边界参考。
3.3 结果记录与重复性控制
lmbench 的输出默认打到 stdout,我习惯重定向到文件并加上时间戳,方便后续对比:
# 记录一次完整延时测试,带时间戳和机器标识 HOST=$(hostname) TS=$(date +%Y%m%d_%H%M%S) ./lat_mem_rd -t 512 64 > lat_mem_rd_${HOST}_${TS}.txt 2>&1逻辑说明:2>&1把 stderr 也收进文件,避免遗漏警告信息。参数上,文件名里带主机名和时间戳,是为了多台机器、多次测试之间不混淆。重复性方面,建议同一配置至少跑 3 次,取中位数,因为单次结果可能受后台任务、温度降频影响。如果三次结果离散度超过 10%,先查系统负载和 CPU 频率策略,别急着下结论。
4. 内存带宽测试:bw_mem 的读写模式与多线程陷阱
4.1 bw_mem 的六种操作模式
bw_mem比lat_mem_rd参数更丰富,它支持多种内存操作模式,每种模式测的带宽含义不同:
| 模式 | 含义 | 典型用途 |
|---|---|---|
| rd | 纯读带宽 | 评估读取密集型负载 |
| wr | 纯写带宽 | 评估写入密集型负载 |
| rdwr | 读写混合 | 模拟一般计算负载 |
| cp | 拷贝(读+写) | 评估 memcpy 类操作 |
| fwr | 写后读回 | 评估写分配策略影响 |
| frd | 读后写回 | 评估读改写场景 |
我一般先跑rd、wr、cp三个,基本能覆盖大部分场景。命令格式是./bw_mem -N <重复次数> <工作集MB> <模式>,其中-N控制每个测试重复多少轮,轮数越多结果越稳,但耗时也越长。
# 工作集 256MB,重复 10 轮,分别测读、写、拷贝带宽 ./bw_mem -N 10 256 rd ./bw_mem -N 10 256 wr ./bw_mem -N 10 256 cp逻辑说明:工作集 256MB 确保超出 LLC,测的是真实内存带宽。-N 10表示每个测试内部重复 10 次取平均,减少瞬时抖动。参数上,工作集不要小于 LLC 的 2 倍,否则缓存命中会拉高带宽数值,看起来漂亮但不真实。
4.2 多线程与绑核:为什么你的带宽只有别人一半
bw_mem默认单线程。单线程带宽受限于单个核心的内存级并行能力(MLP),通常只能跑到内存控制器峰值的一小部分。要测出内存子系统的真实上限,需要多实例并行,并且把每个实例绑到不同物理核心上。
# 启动 4 个 bw_mem 实例,分别绑定到 CPU 0、1、2、3 for cpu in 0 1 2 3; do taskset -c $cpu ./bw_mem -N 10 256 rd > bw_rd_cpu${cpu}.txt 2>&1 & done wait # 汇总四个实例的带宽,得到近似总带宽 grep -h "rd" bw_rd_cpu*.txt逻辑说明:taskset -c把进程绑定到指定 CPU,避免调度器把多个实例挤到同一个核心上。&后台启动,wait等全部结束。参数上,实例数不要超过物理核心数,超线程核心共享执行单元,加了反而互相拖累。汇总时把各实例带宽相加,得到的是近似总带宽,实际会略低于理论峰值,因为有内存控制器争用开销。
注意:如果你在虚拟机或容器里跑,绑核可能无效或绑到的是虚拟 CPU,带宽结果参考价值有限。物理机裸跑最准,容器里至少确认 cpuset 是否生效。
4.3 带宽结果的合理区间与异常判断
以主流双通道 DDR4-3200 为例,理论峰值约 51.2 GB/s。单线程rd通常能跑到 10 到 15 GB/s,四线程并行能到 30 到 40 GB/s,接近但达不到峰值。如果你的四线程结果只有 15 GB/s,先查三件事:是否绑到了同一物理核心、工作集是否太小导致缓存命中、BIOS 里内存频率是否跑在默认而非 XMP/EXPO 档位。这三条是血泪经验里出现频率最高的。
5. 避坑与排查:lmbench 结果不可信的五个常见原因
5.1 现象:延时曲线在 L3 处没有平台,一路平滑上升
原因:stride 设置过小,比如用了 16 或 32 字节,导致多次访问落在同一 cache line,预取器把后续数据提前拉进缓存,掩盖了真实的缓存层级边界。解决:把 stride 改成 64 或 128,重新跑。如果仍然平滑,检查 CPU 是否禁用了预取器,或者工作集步进太粗,漏掉了拐点区间。
5.2 现象:带宽数值远高于内存理论峰值
原因:工作集太小,整个测试跑在 LLC 里,测的是缓存带宽而非内存带宽。比如 256MB 工作集在 LLC 为 64MB 的机器上没问题,但在 LLC 为 256MB 的服务器上就全在缓存里。解决:工作集至少设为 LLC 容量的 4 倍,不确定 LLC 大小时用lscpu查L3 cache字段,然后乘 4。
5.3 现象:同一台机器两次测试结果差异超过 20%
原因:CPU 频率调节策略在跑分时降频,或者后台有其它任务争抢内存带宽。解决:测试前把 CPU governor 设为 performance,关闭不必要的后台服务,用numactl或taskset隔离测试核心。如果还不行,检查是否开启了透明大页(THP),THP 的合并和拆分会在测试过程中引入抖动,临时关闭再测。
5.4 现象:NUMA 机器上延时测试结果比预期低很多
原因:没有绑核绑内存,操作系统把测试进程调度到了离内存最远的节点,或者内存分配跨了多个节点。解决:用numactl --cpunodebind=N --membind=N强制本地访问,再对比远端访问结果。如果本地和远端差值很小,说明 BIOS 里 NUMA 被关了,或者机器实际上是单节点架构。
5.5 现象:编译报错undefined reference to tirpc相关符号
原因:新系统上 RPC 库被拆到libtirpc,lmbench 的 Makefile 没有自动链接。解决:安装libtirpc-dev后,在src/Makefile的LDLIBS里加上-ltirpc,或者直接make LDFLAGS=-ltirpc。如果只关心内存测试,也可以只编译lat_mem_rd和bw_mem两个目标,绕过 RPC 依赖。
6. 进阶技巧:用 lmbench 做机型对比与调优验证
6.1 建立可复现的测试基线
单次测试没有意义,有意义的是同一套参数下的横向对比。我一般会写一个固定参数的测试脚本,把延时和带宽的关键指标抽出来,存成 CSV,方便多台机器之间对齐。脚本核心逻辑如下:
#!/bin/bash # lmbench_mem_baseline.sh - 固定参数采集内存延时与带宽基线 BIN=./bin/$(uname -m)-linux-gnu WS=512 # 工作集 MB,需大于 LLC 的 4 倍 STRIDE=64 # stride 字节 REPEAT=10 # 带宽测试重复轮数 # 延时:取 512MB 工作集处的延时值 LAT=$($BIN/lat_mem_rd -t $WS $STRIDE | tail -1 | awk '{print $2}') # 带宽:单线程读、写、拷贝 BW_RD=$($BIN/bw_mem -N $REPEAT $WS rd | tail -1 | awk '{print $2}') BW_WR=$($BIN/bw_mem -N $REPEAT $WS wr | tail -1 | awk '{print $2}') BW_CP=$($BIN/bw_mem -N $REPEAT $WS cp | tail -1 | awk '{print $2}') echo "$(hostname),$LAT,$BW_RD,$BW_WR,$BW_CP"逻辑说明:tail -1取输出最后一行,即最大工作集对应的结果,代表纯内存访问。awk '{print $2}'取第二列数值。参数上,WS和STRIDE必须固定,否则不同机器之间不可比。这个脚本输出一行 CSV,多台机器跑完拼起来就是一张对比表。
6.2 用延时差值判断 NUMA 配置是否生效
NUMA 机器上,本地和远端访问的延时差值是一个关键指标。差值明显(比如本地 80ns、远端 140ns),说明 NUMA 生效且跨节点代价真实存在;差值很小(比如都在 90ns 左右),说明 NUMA 被关闭或内存交错分配了。验证方法:
# 本地访问 numactl --cpunodebind=0 --membind=0 $BIN/lat_mem_rd -t 512 64 | tail -1 # 远端访问 numactl --cpunodebind=0 --membind=1 $BIN/lat_mem_rd -t 512 64 | tail -1两次结果的差值就是跨节点延时。如果差值为零或极小,去 BIOS 里确认 Node Interleaving 是否被打开,打开后内存会跨节点交错,延时趋于平均,但带宽可能更稳。这是调优时的取舍,没有绝对好坏,看你的负载是延时敏感还是带宽敏感。
6.3 验证大页与内存频率调优效果
调优内存性能时,透明大页(THP)和内存频率是两个常见抓手。用 lmbench 可以快速验证改动是否有效:先跑基线,改配置,再跑一次,对比延时和带宽。比如开启 THP 后,TLB miss 减少,延时曲线在大工作集处可能略有下降;把内存频率从 2400 提到 3200,带宽通常有 20% 到 30% 提升,延时会小幅下降。
我自己的习惯是:任何内存相关的调优,改完必须用 lmbench 跑一遍固定参数的延时和带宽,和改之前对比。不看广告看疗效,跑分软件的花哨图表不如 lmbench 一张朴素的延时表来得实在。从那以后我每次上架新机型或者改 BIOS 内存设置,都强制走一遍这套基线脚本,省得后面应用出问题再回头扯皮。希望帮到你。
本文还有配套的精品资源,点击获取