简介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 到 512MBstride 64覆盖 L1/L2/L3/内存四个层级 ./lat_mem_rd -t 512 64 # 同样范围stride 128观察 stride 翻倍后延时曲线的变化 ./lat_mem_rd -t 512 128逻辑说明-t 512表示最大工作集 512MBlmbench 会自动从较小的工作集开始逐步增大输出一条完整的延时曲线。参数上工作集上限建议设为 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 --cpunodebind0 --membind0 ./lat_mem_rd -t 512 64 # 绑定 CPU 到 node 0但内存只从 node 1 分配测远端访问延时 numactl --cpunodebind0 --membind1 ./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 21逻辑说明21把 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 21 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隔离测试核心。如果还不行检查是否开启了透明大页THPTHP 的合并和拆分会在测试过程中引入抖动临时关闭再测。5.4 现象NUMA 机器上延时测试结果比预期低很多原因没有绑核绑内存操作系统把测试进程调度到了离内存最远的节点或者内存分配跨了多个节点。解决用numactl --cpunodebindN --membindN强制本地访问再对比远端访问结果。如果本地和远端差值很小说明 BIOS 里 NUMA 被关了或者机器实际上是单节点架构。5.5 现象编译报错undefined reference to tirpc相关符号原因新系统上 RPC 库被拆到libtirpclmbench 的 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 WS512 # 工作集 MB需大于 LLC 的 4 倍 STRIDE64 # stride 字节 REPEAT10 # 带宽测试重复轮数 # 延时取 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 --cpunodebind0 --membind0 $BIN/lat_mem_rd -t 512 64 | tail -1 # 远端访问 numactl --cpunodebind0 --membind1 $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 内存设置都强制走一遍这套基线脚本省得后面应用出问题再回头扯皮。希望帮到你。本文还有配套的精品资源点击获取