☰
lmbench-3.0内存性能测试:带宽与延迟基准实战
2026/10/29 19:18:40 网站建设 项目流程

简介: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) 2>&1 | 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 see

make 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 次。输出格式是两列,第一列是探测的数组大小,第二列是对应的访问延迟(单位 ns):

"stride=128" 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 最重要的参数。stride=1 时遍历的是连续内存,顺序访问会被硬件预取器识别,测出来的延迟是整个 cache line 的加载时间,可能只有十几 ns;stride=128 时,每次访问都需要重新查找 cache line,但相邻访问仍可能在同一个内存 bank,存在一定的 bank-level parallelism;stride 继续增大到 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 是 32KB;256KB 到 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 bs=1M count=512这种凑巧的预写操作。

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 --membind=0 --cpunodebind=0 ./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_mem+lat_mem的十分钟快速基准,结果存成带日期和主机名的文本文件。下次有人再报“内存变慢了”,我直接调三个月前同参数的数据对比,一次就能说清楚是应用层问题、系统层变化还是硬件老化。希望这套流程和参数经验帮到你,省下你在微基准上反复试错的几周时间。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询