简介lmbench-3.0是一套经典的开源系统性能基准测试工具覆盖内存带宽、内存延时及基础计算性能测试适合系统管理员、开发者和硬件工程师用于性能诊断与软硬件对比。资源包内含225个文件核心源码为63个C文件并配有头文件、Makefile、man手册及表格配置文件整体仅508KB层次清晰便于阅读和交叉编译。目前已有3660人学习/下载。通过该资源可获取完整的源码与构建配置在Linux及Unix-like系统上编译运行后能输出内存读写带宽、访问延迟等关键指标用于定位性能瓶颈、验证优化效果或评估不同硬件平台。该工具支持多线程测试并允许自定义数据块大小与迭代次数便于针对具体场景调优附带的手册页和示例配置也提供了命令参考与参数选择指引即使初次接触基准测试也能按文档快速完成测试并解读结果。1. lmbench-3.0先别猜内存慢在哪跑一组微基准再说内存延迟和内存带宽不是网上找几个“感觉靠谱”的数字就能交差的。我之前接过一个性能排查的活儿业务方一口咬定数据库响应慢是磁盘问题结果压测数据摆出来瓶颈在 NUMA 内存访问延迟上磁盘根本没满。当时用的就是 lmbench-3.0 这个老牌微基准工具套件。它体积不大、依赖极少适合在任何 Linux 机器上快速编译能把内存读带宽、写带宽、复制带宽、随机访问延迟、cache 命中等指标一次性量化出来。项目里的核心工具是bw_mem和lat_mem前者测带宽后者测延迟这两个工具的参数设计和结果读法都有讲究。适合的人很明确做性能优化、搞数据库选型、调优 JVM/Redis 这类吃内存的中间件或者只是想在买服务器前对比几台机器真实内存性能的从业者。这篇就把我在 x86 和 ARM 机器上折腾 lmbench-3.0 的完整过程、参数含义和踩过的坑铺开讲。2. 编译与首次运行lmbench-3.0 的安装比想象中麻烦一点2.1 源码包结构与编译逻辑lmbench-3.0 的源码包解压之后顶层目录不是标准的 autotools 结构而是它自己的一套Makefile体系。核心目录是src/里面有三个子目录lmbench/放着bw_mem、lat_mem、lat_mem2d、lat_ctx等主程序scripts/放着一堆 shell 和 awk 脚本results/默认用来存测试结果。编译之前要理解它默认的make动作它会尝试探测当前系统的处理器频率、cache 大小、操作系统版本生成一个名为config的配置文件再根据配置去编所有工具。问题在于很多新系统的默认编译器比如 GCC 12对 lmbench-3.0 这种 2005 年前后停更的代码并不友好直接make经常会碰到隐式声明报错。我一般会在src/目录下先执行make -j$(nproc) 21 | tee lmbench-build.log这里的-j$(nproc)是让编译器用上所有 CPU 核心tee lmbench-build.log是为了把编译输出存下来等下如果报错直接去翻日志定位。如果看到error: implicit declaration of function alloca这类错误不要慌常见做法是在src/Makefile里的CFLAGS那行补上-D_GNU_SOURCE -O2然后清理重编make clean make CFLAGS-O2 -D_GNU_SOURCE -j$(nproc)-D_GNU_SOURCE的作用是开启 GNU 扩展让alloca()、strdupa()这些函数能通过隐式声明关-O2是标准优化级别对微基准来说-O2是底线——太低结果失真太高编译器可能会把测内存的小循环自动向量化测出来的带宽比你机器真实水平高出一截。编译成功后在src/下会生成bw_mem、lat_mem等可执行文件它们就是我们要用的主角。2.2 先用默认方式跑一套完整基准lmbench-3.0 提供了一套编排脚本直接跑全部测试的话会用scripts/下的lmbench驱动脚本进入图形化或批处理模式。日常排查我强烈建议走批处理交互模式在无显示环境的机器上会卡住make results这条命令会跑完整套测试包括处理器、文件系统、内存带宽、内存延迟、上下文切换等。它的结果不是直接打印在终端而是写到results/目录下按主机名命名的文件里。要查看用make seemake see会调用脚本把原始结果整理成可读的表格打印出来。第一次跑完你可能会觉得输出很怪——内存延迟部分会打印出很多档位从 1k 到 512m这是正常的lmbench 的lat_mem会把数组从 1KB 一直加大到几百 MB来观察延迟随容量变化的曲线。我个人的习惯正式压测之前先跑一遍make results来验证工具链和系统状态是正常的。如果这一条命令能跑完且make see能出结果说明编译、权限、CPU 频率探测都正常。但如果make results跑得很慢比如一个档位卡了几十秒多半是 CPU 频率探测脚本在虚拟机上出了问题可以跳过整套脚本直接用单独的命令测内存指标。2.3 单独跑 bw_mem 的整条链路我第一次单独跑内存带宽时直接敲了./bw_mem 16m rd结果就一行数字完全看不出门道。后来才搞清楚bw_mem的原生输出就是两个数MB 数和带宽值。实际上要得到理想结果最好用脚本带上参数。举个例子./bw_mem -M 16m -N 20 -P 1 -W 100 -L 50 512m rd这里参数含义分别是-M 16m表示以 16MB 为分块单位-N 20是迭代 20 次-P 1是使用 1 个进程-W 100是每次分配后的预热秒数-L 50是每次测试运行的最长秒数。512m是测试缓冲总大小rd代表读测试。这条命令的完整语义是把 512MB 的缓冲区按 16MB 分块做读测试每个分块反复读 20 轮总共耗时最长 50 秒。之所以要分块是为了避免 512MB 一次性全放在 TLB 里形成极高的 TLB 命中率导致带宽虚高。你如果要快速拿到数字跑下面这条就够./bw_mem -P 1 -N 100 32m rd-P 1固定单进程-N 100迭代一百次取中间值32MB 缓冲区比 L3 cache 大但远比物理内存小能测出真实内存读吞吐量。这套单跑命令不需要走make results是后续所有带宽排查的基础。3. 内存带宽测试bw_mem 的副本模式与块大小到底怎么选3.1 bw_mem 的六种测试模式逐个拆bw_mem这个名字看起来朴实其实它内部实现了六种不同的内存访问模式rd读、wr写、rdwr读写交错、cp拷贝、fcp快速拷贝、bzero清零。六种模式的访问特征完全不同测出来的数字也从几百 MB/s 到几十 GB/s 不等不能拿一个数概括“内存带宽”。rd模式就是单纯地对缓冲区做连续读从内存读入寄存器但不写回。这个测试能反映内存读带宽的理论上限最容易受到编译器优化影响——如果编译器发现读出来的结果从没被使用可能直接把整个循环优化掉。lmbench 的做法是在循环里对累加值做一次printf但老代码在部分新内核上仍然会被编译器部分优化掉。这就是为什么-N迭代次数要设得足够大同时建议打开-W预热。wr模式是对每个字做写操作它测的是写带宽。写操作在现代 CPU 上不会阻塞到数据真正落内存而是进了 write-back 缓存后由 cache line 状态决定何时下写你的进程看到的是后半段速度。rdwr是交替读和写反映的是混合负载下的带宽和数据库里的页脏写场景接近。cp是标准拷贝从一个缓冲区拷贝到另一个读 1 字节写 1 字节能反映memcpy的典型速度。fcp是快速拷贝模式使用了非临时写指令如movnti绕过缓存直接写到内存速度通常比cp要高但会污染 cache 状态这一条要小心测完它之后测延迟要重新预热。bzero模拟的是把一块内存清零这个模式对物理内存页的初次分配行为非常敏感——如果测试缓冲区是新分配的清零时触发的缺页中断会显着拉低带宽。常见做法是先对缓冲区写一遍把它”预热“到物理页全部就位再跑 bzero否则数字会比真实水平低 20% 以上。3.2 bw_mem 参数表每个参数改动的真实代价参数含义典型值改动的风险-M size分块大小4m / 16m / 64m小于 L3 cache 时测的是 cache 带宽不是内存带宽-N iterations每块迭代次数10 ~ 200太小噪音大太大测试时间急剧拉长-P processes同时跑测试的进程数1 ~ 8多进程能叠加吞吐但受内存控制器通道数限制别盲目加大-W seconds每块测试前的预热秒数100 左右加了预热能稳定频率但测试总时长会成倍增加-L seconds每块最长允许测试秒数50 ~ 300防止迭代次数太多导致跑一晚上末尾size缓冲区总大小当前系统物理内存的 1/4 ~ 1/2超过物理内存会触发 swap结果直接失真mode访问模式rd / wr / rdwr / cp / fcp / bzero不同模式测的是不同维度别拿着写带宽对比别人的读带宽这里特别强调最后一个坑也是我翻车最多的一次。在 64GB 的机器上跑./bw_mem -M 4m -N 10 48g rd结果跑出来只有几百 MB/s还不如一块企业级 SSD 的持续读速度。查了才知道48GB 缓冲区超过了物理内存余量系统开始疯狂 swap。从那以后我强制自己把测试总内存控制在物理内存的一半以下并在跑之前用free -g确认一次。3.3 用脚本一口气测出六种模式单条命令只能测一种模式但实际做性能对比时往往是六种模式全部测一遍。我写了一个简单的 shell 循环把结果以 CSV 格式输出方便后续画图或直接塞进表格#!/bin/bash # 文件: bw_all_modes.sh for mode in rd wr rdwr cp fcp bzero; do echo mode: $mode # 物理内存的一半以内用 512MB 缓冲区单进程每块 4MB迭代 50 次 ./bw_mem -P 1 -M 4m -N 50 512m $mode done这里参数设计基于三个原则512MB 缓冲区在绝大多数服务器上都远大于 L3 cache能测到真实内存带宽4MB 的分块大小保证了每次遍历都在 Huge TLB 范围内50 次迭代足以让结果稳定下来。如果机器内存特别小比如 2GB把 512m 改成 128m 也够用。跑完之后你会发现同一种模式下不同大小的缓冲区结果差异极大。缓冲区 4KB 时读带宽可能有 40GB/s 以上因为整个缓冲区都驻留在 L1 cache到 8MB 时掉到 20GB/s到 64MB 以上才稳定在真实内存带宽。如果你拿 4KB 缓冲区的数字说这台机器内存带宽很高那是在拿 cache 带宽冒充血统。真正值得记录的数字是缓冲区约等于内存 1/4~1/2 那一档。4. 内存延迟测试lat_mem 的数组步长与随机化原理4.1 lat_mem 测的到底是什么延迟带宽测试解决的是“能搬多少数据”延迟测试解决的是“一次访问要等多久”。内存延迟不是恒定值它高度依赖访问模式。lmbench 的lat_mem用的是一个经典的指针追逐测试它先在内存里创建一个大数组然后按固定步长 stride 去遍历每次访问都是读取一个指针再跳到下一个位置。运行方式./lat_mem -P 1 -N 50 -M 32m 128最后面的128表示 stride 为 128 字节。-M 32m是缓冲区大小 32MB-N 50为迭代 50 次。输出格式是两列第一列是探测的数组大小第二列是对应的访问延迟单位 nsstride128 1 1.048 2 1.048 4 1.048 ... 32m 87.21 64 71.03 ...注意看这个输出前两行的小数组结果1KB、2KB是 1.048ns这不代表你的内存有 1ns 延迟而是测试程序在数组太小、完全在 cache 里时的预热结果。我记得 lmbench 官方说明里明确讲过第一二档的数据要直接忽略。真正的内存延迟出现在数组大小远大于最后一级 cache 之后比如这台机器在 64MB 档位看到的 71.03ns才是无 cache 覆盖时的真实 DRAM 延迟。4.2 stride 怎么影响延迟读数stride 是 lat_mem 最重要的参数。stride1 时遍历的是连续内存顺序访问会被硬件预取器识别测出来的延迟是整个 cache line 的加载时间可能只有十几 nsstride128 时每次访问都需要重新查找 cache line但相邻访问仍可能在同一个内存 bank存在一定的 bank-level parallelismstride 继续增大到 512 甚至 1024访问会在多个内存 bank 之间跳跃测出来的才是随机访问延迟。实战中我常用两组组合一组是./lat_mem -P 1 -N 50 -M 64m 64测顺序访问延迟一组是./lat_mem -P 1 -N 50 -M 64m 256测随机访问延迟。报告数字时一定同时注明 stride不然 64 和 256 的结果能差 30%别人根本没法复现你的结论。lmbench-3.0 源码包里还有一个lat_mem2d工具它做的是二维数组的跨页访问每次迭代都强制访问不同的物理页。这个工具更接近数据库随机 IO 的内存访问特征但它在 x86 现代 CPU 上的表现不太稳定我一般只在 ARM 服务器上用它做补充验证。普通场景lat_mem就够。4.3 结果怎么读cache 层级一目了然lat_mem的输出天然形成了所谓“阶梯”曲线当数组大小小于 L1 cache 时延迟平稳在几 ns跨越 L1 后跳到十几 ns跨越 L2 再跳到三四倍跨越 L3 后跳到 50~100ns 区间。这条曲线的拐点位置实际上就是把你的 cache 容量和延迟关系画在了一个表格里。我在实测一台 AMD EPYC 机器时输出里有个明显的特征8KB 到 32KB 那一档延迟没有爬升说明 L1 data cache 是 32KB256KB 到 512KB 档有一次跳变说明单核 L2 是 512KB到 8MB 到 32MB 档有一次大的跳变代表 L3 共享 cache 大约 32MB。这些信息和/sys/devices/system/cpu/cpu0/cache/index*/size完全对得上。这个表的最大用处是做回归对比。比如系统更新内核后觉得内存变慢了跑一套lat_mem发现 32MB 档的延迟从 75ns 涨到 91ns那说明不是业务代码的锅而是内存层面的变化。这类结论在仲裁“是应用慢还是系统慢”这种问题时非常有说服力。5. 避坑与排查跑 lmbench 翻过的五类车5.1 现象延迟结果几十毫秒看起来像死机现象lat_mem -M 512m 128跑起来后某个档位的延迟显示成几十毫秒程序卡在那里不动。原因缓冲区 512MB 超过了机器可用物理内存系统开始大量换页。特别是云主机内存超卖严重512MB 缓冲区能触发宿主机级别的 swap。解决跑之前用free -m看一眼可用内存缓冲区总量不超过可用内存的一半。我现在的习惯是固定-M 128m或-M 64m除非明确在测超大内存场景否则不轻易超过 256MB。5.2 现象连续两次同样的命令结果差 20% 以上现象bw_mem -P 1 -N 20 512m rd连续跑两遍第一次 18GB/s第二次 12GB/s毫无规律。原因CPU 频率没有锁死。现在大多数服务器默认启用了动态调频负载高时频率升到 4GHz空闲时降到 2.5GHz。微基准测试本身就是高负载但它在每次迭代之间的空档会触发降频。解决用cpupower frequency-set -g performance把调频模式设为 performance跑完再改回 ondemand。虚拟机环境不能改宿主机频率就只能用-W 100预热 -N加大迭代次数让频率尽量稳定在测试区间。另外跑之前建议关掉其他业务进程用top看一眼有没有 CPU 占用率超过 1% 的进程在抢资源。5.3 现象bzero 模式测出来比 wr 模式还快现象同一台机器上bw_mem 512m wr只有 8GB/s但bw_mem 512m bzero反而是 15GB/s这明显不合逻辑。原因缓冲区的物理页在第一次写入时全部是写时复制的新页写操作会触发 page fault每 4KB 都要走一遍内核缺页处理而bzero在测试前对缓冲区做了一次显式的页预分配mmap 后立刻写入后续清零操作全部命中已分配的页。解决想测真实的清零带宽必须在测试前手动把缓冲区预写一遍。我用一个小手段先跑一次./bw_mem -P 1 -N 1 512m wr让内核把全部物理页分配好再跑./bw_mem -P 1 -N 100 512m bzero。或者直接在测试脚本里先执行dd if/dev/zero of/tmp/prefill bs1M count512这种凑巧的预写操作。5.4 现象make results卡在 CPU 频率探测现象在 Docker 容器或某些云主机上执行make results脚本会卡在CPU frequency detection环节终端无输出测试无法继续。原因scripts/下有一个根据/proc/cpuinfo中的cpu MHz字段或rdtsc指令推算频率的脚本。容器环境里/proc/cpuinfo的 MHz 经常是虚拟值脚本的自校验逻辑不通过就会无限重试。解决不做整套make results直接手动运行bw_mem/lat_mem。如果确实需要整套结果可以把scripts/里的频率探测先临时跳过修改scripts/lmbench脚本中调用的get_cpu_frequency让它打印一个固定值比如 3000再恢复原逻辑。5.5 现象结果好看但可信度存疑现象bw_mem测出来 25GB/s比同型号物理机高了 30%怎么看都太漂亮了。原因编译器做过手脚或者测试缓冲区被透明大页THP和后端不匹配的 NUMA 分配弄出了一些“优化效果”。也可能是这块缓冲区在内存上恰好分布在同一个 NUMA node 上跨 node 访问被绕开了。解决看到异常好看的带宽结果先用numactl --hardware看下内存通道分布再用numactl --membind0 --cpunodebind0 ./bw_mem -P 1 -N 100 512m rd强制把进程绑在 node0 上重测。绑核后的数字才是单节点真实能力不绑核测出来的多节点叠加带宽只适合评估整体吞吐不能直接当单进程延迟来引用。6. 进阶验证用多轮对比定位内存性能退化的根源单次跑到稳定数字只是第一步真正有价值的用法是把 lmbench-3.0 当作回归基准隔一段时间跑一遍然后对比数据找变化点。我在三台配置几乎一样的服务器上做过一次对比结果第一台比另两台的内存延迟高了 18%。因为硬件型号一致用lat_mem先排出 cache 容量差异再查 BIOS 才发现这台机器启用了某个电源管理方案把内存控制器降频了。实操时我的做法是在每台机器上固定跑三组命令全部加上-P 1 -N 200保证低噪音然后把输出重定向到文件./bw_mem -P 1 -N 200 -W 100 64m rd mem_bw_rd.log ./bw_mem -P 1 -N 200 -W 100 64m wr mem_bw_wr.log ./lat_mem -P 1 -N 200 -M 64m 128 mem_lat_128.log对比时我关心三个数字64MB 档的读带宽、64MB 档的写带宽、数组 64MB 时的延迟。这三个数字对应了“内存能不能快速搬数据”和“单次访问要等多久”两个维度。带宽数字异常但延迟正常说明内存通道或互连有问题延迟异常高但带宽正常通常是访问局部性差、TLB 命中率低或者 NUMA 远端访问占比过高。如果三次跑批的数字和上月相比波动超过 5%我会先做一次dmesg检查有没有硬件错误再看内存温度是不是偏高很多服务器能显式温度。这套流程已经把好几台“看似没问题但总是慢半拍”的机器拉回正轨也帮我在汇报性能结论时省去了大量争论——因为每个数字背后都有明确的命令、参数和可复跑的日志。从那以后我每接手一台新服务器或者每次调内核参数都会强制走一遍bw_memlat_mem的十分钟快速基准结果存成带日期和主机名的文本文件。下次有人再报“内存变慢了”我直接调三个月前同参数的数据对比一次就能说清楚是应用层问题、系统层变化还是硬件老化。希望这套流程和参数经验帮到你省下你在微基准上反复试错的几周时间。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?