用 lmbench 测透系统性能底裤:内存带宽、延迟与进程开销实操指南 简介lmbench-3.0是一款适用于Linux及Unix-like系统的开源性能基准测试工具定位在内存带宽与延迟专项评测及系统综合性能摸底适合系统管理员、开发者和硬件工程师用于性能诊断与调优验证。压缩包收录了225个文件核心为60余个C源码文件搭配头文件、Makefile与构建脚本方便直接编译与二次开发同时包含大量man手册、格式说明文档和测试结果分析辅助脚本整体仅508KB轻量便携。目前已有3662人学习下载。资源不仅提供可运行的lmbench源码还附带结果收集、图表生成和上下文切换等配套工具读者可借此完整跑通内存测试、理解带宽与延迟指标含义并根据自身环境调整测试参数作为性能优化的重要参考。1. lmbench-3.0一套 30 秒就能撕开系统性能底裤的微基准套件第一次在嵌入式板子上跑 lmbench-3.0 时我用它只花了三分钟就查出了问题宣传标称 DDR 带宽能到 3200 MT/s业务应用却卡顿明显bw_mem一测实际内存拷贝带宽只有标称的一半。这套诞生于 90 年代的微基准测试工具集如今仍是量化操作系统底层开销最直接的手段。它测的是“硬件和内核配合的极限”——内存带宽、内存延迟、上下文切换、进程创建、信号处理、管道吞吐每一项都用极短时间的微操作把系统底层的真实成本暴露出来。对于做嵌入式选型、服务器性能基线、内核调参前后对比的工程师来说它比跑几遍应用压测更容易定位瓶颈到底在 CPU、内存还是内核路径上。这一篇我就照着 3.0 源码包带你从编译一路走到结果解读和避坑全部是能直接抄走的实操路径。2. 编译与最小部署从源码到第一份报告的完整流程2.1 选型理由为什么还在用 20 年前的套件而不是整机基准工具做性能评估时经常被反问为什么不直接上整机压测工具我的看法是工具定位不同。整机基准比如 sysbench、iozone、phoronix-test-suite 测的是“应用视角的系统表现”结果受文件系统、调度策略、缓存命中率影响很大一次跑完你很难说清瓶颈在哪个子系统。而 lmbench-3.0 的定位是微基准它把操作系统的基础操作拆成单点比如 fork 一次要多少微秒、读一个脏 cache line 要多少纳秒、两进程切换一次要付出多大代价。微基准测试的问题是“每个数字都很小”但恰恰是这些小数点后的微秒级数据决定了高并发应用在高负载下的宏观表现。选 lmbench 而不是自己写测试脚本的原因有三点一是它把测试规模、重复次数、计时方式都已经调校过你不需要自己处理计时器的边界条件二是单二进制、无运行时依赖拷到目标机上就能跑对嵌入式交叉环境友好三是结果文件是纯文本方便做回归对比。整机基准工具适合“这台机器整体快不快”的评价lmbench 适合回答“这个子系统为什么慢”的追问。2.2 编译前准备源码结构、工具链要求和环境变量源码包拿到手后先别急着 make先看目录结构。根目录下有src/、scripts/、doc/真正的 benchmark 全在src/下每个测试是独立的 C 文件比如bw_mem.c、lat_mem_rd.c、lat_ctx.c公共代码在lmbench.h和bench.h里。scripts/下是辅助脚本getres提取结果、summary做汇总make results整套流程依赖这些 perl 脚本。编译条件比想象中低gcc、make、perl三个齐了就能编。不需要 configure因为它根本没有 autoconf 那套东西Makefile 会在编译时自动探测操作系统。我一般会在编译前确认一下当前系统的 libc 版本因为 3.0 的代码比较老在很新的发行版上直接把 make 命令扔下去大概率会报几个结构体未定义这个坑后面专门用一章来排。2.3 编译命令与首次跑测make 与 make results 的最小流程最常用的编译流程是直接进src/目录然后显式指定操作系统类型cd lmbench-3.0/src make OSlinuxOSlinux是显式告诉构建系统目标平台避免它在某些精简容器或新发行版上自动探测出错。编译产物是一堆不带后缀的可执行文件直接位于src/目录下。如果编译报错先看是不是缺头文件再看是不是 libc 兼容问题常见的修法在第五章给出。编译通过后跑整套测试make results make seemake results会执行完整测试集包括内存带宽阶梯、文件读取、网络延迟、进程创建等视机器性能耗时 5 到 20 分钟不等。期间终端会不断刷各个子测试的输出看着像报错其实大部分是正常进度打印。跑完后make see会把结果整理成可读表格显示在终端。第一次跑完你手里就有一份这台机器全面的性能档案了。如果不想等全套可以单独跑某个二进制比如我只想快速验证内存拷贝带宽./bw_mem 64M 1000 copy第一个参数 64M 是测试用内存块大小第二个参数 1000 是重复次数第三个参数 copy 表示拷贝操作。命令输出的两列分别是测试规模和带宽单位是 MB/s。我一般先用这种单点命令确认二进制能跑、结果不是全 0再放心去跑全套。2.4 结果文件在哪里results 目录与命名规则测试结果不只在终端展示还会落盘。在源码包根目录下会自动生成results/目录里面每个文件按主机名.日期时间命名例如worker01.20241205-1530。这个文件是纯文本记录了整套测试的输出。后面做对比时把它复制一份改名即可不需要重新跑测试。make see本质上是调用了scripts/getres来格式化展示。你也可以直接手动调用这个脚本单独提取某个 benchmark 的数据./scripts/getres -b bw_mem results/worker01.20241205-1530-b参数指定要看哪个测试项输出的就是该测试的原始数据适合丢进 Excel 或直接 diff。这一步很常用建议在拿到结果文件后先手动试一次理解 getres 的结构后面写回归脚本会顺手很多。3. 核心 Benchmark 分组带宽、延迟、进程开销的测法与参数含义3.1 内存带宽三件套bw_mem 的操作参数与 size 选择bw_mem是使用频率最高的一个测试四种内存操作read、write、copy、rdwr。前两者是单向读写copy是从一块内存拷贝到另一块rdwr是交替读写同一区域。四条命令分别测一次./bw_mem 16M 2000 read ./bw_mem 16M 2000 write ./bw_mem 16M 2000 copy ./bw_mem 16M 2000 rdwr这里 16M 指分配 16MB 的缓冲区2000 是操作次数。size 的选取有讲究如果缓冲区小于 CPU 的最后一级缓存测出来的是缓存带宽不是内存带宽。要测真实内存带宽size 必须超过 LLC 容量我一般取物理内存的四分之一到二分之一。比如机器有 8GB 内存用 2G 到 4G 之间的值比较合理。bw_mem的输出格式是两列第一列是实际分配的字节数用十进制 MB 表示第二列是带宽。注意copy操作实际需要分配两块缓冲区所以内存占用是两倍的 size32 位系统上如果 size 设太大可能直接分配失败这一点后面避坑章会细说。3.2 内存延迟的阶梯图lat_mem_rd 的 stride 与 line size 怎么配带宽和延迟是内存性能的两个维度lat_mem_rd测的是延迟。它比bw_mem难懂一点因为它要测的是“在不同工作集大小下一次随机读访问需要多少纳秒”以此勾勒出缓存层级./lat_mem_rd 256M 128 64 1四个参数的含义256M 是最大测试范围128 是 stride步长单位字节64 是 cache line 大小1 是重复次数。stride 是很多新手看不懂的参数——它控制每次访问地址的间隔。为什么不能顺序读而要隔 128 字节跳着读因为顺序读时硬件预取器会帮你把后面的数据提前拉到缓存测出来的延迟是预取后的假象不是真实随机访问延迟。stride 取 128 是为了跨过 cache line 边界强制每次访问都查一次 TLB 和 cache tag这样得到的延迟才是“冷冰冰”的真实延迟。line size 参数一般设成 64对应主流架构的 cache line 长度。如果机器是 ARM 或者新的 Intel 平台可以先通过getconf -a | grep CACHE查一下真实的 cache line 大小再填。重复次数默认 1 够了因为这个测试内部已经有足够多的访问次数。输出是一个多行的阶梯表每行两个字段左边是当前工作集大小从 1KB 一直增长到 256M右边是对应的延迟纳秒数。观察这张表能看到明显的台阶工作集从 32K 涨到 256K 时延迟跳变那是 L1 到 L2 的边界从 1M 到 8M 再跳一次那是 L2 到 L3 的边界最后在 64M 以上基本稳定在高位那就是真实内存延迟。这张表排障时特别好用——如果板子宣称有 2MB L2 但延迟表在 512K 就提前跳变说明缓存容量参数配错了。3.3 进程与上下文切换lat_ctx、lat_fork、lat_exec 的调用约定进程开销是操作系统的“隐形成本”应用层几乎看不到但高并发下线程频繁创建销毁时这些成本会直接变成 CPU 占用。lat_fork测的是 fork 加 exit 的耗时lat_exec额外包含 exec 一个新程序的开销./lat_fork 10000 ./lat_exec 1000参数是重复次数。这两个命令的输出单位是微秒表示单次操作的平均耗时。注意数值可能极小比如 fork 可能只要几十微秒甚至更低如果出来的结果是 0 或者 NAN说明重复次数太少导致计时分辨率不够把次数调大即可。lat_ctx测的是上下文切换延迟它的参数格式不一样./lat_ctx -P 16 -N 20 4 16 64 256-P 16表示启动 16 个进程并行切换-N 20是每对进程来回切换的次数后面跟的 4、16、64、256 是每个进程的工作集大小单位 KB。工作集越大切换后 cache 命中率越低上下文切换代价越高所以这个命令会输出多行数据每个 size 一行。跑这个测试时要注意-P进程数不要超过 CPU 核数太多否则进程调度本身会成为瓶颈测出来的不是上下文切换而是调度器开销数字会异常难看。3.4 文件与网络bw_file_rd、bw_pipe、lat_tcp 适合什么时候用这三个测的是 IO 路径的极限能力使用频率略低但在特定场景很有价值。bw_file_rd测文件顺序读带宽它比直接看应用的 IO 吞吐更纯粹因为它绕过了应用层缓冲./bw_file_rd 16M 500 /tmp/testfile第一个参数是每次读的块大小第二个是重复次数第三个是测试文件路径。测试文件需要提前准备好大小最好超过内存的两倍否则全部命中 page cache测到的是内存速度而不是磁盘速度。这个测试能帮你快速区分“瓶颈在存储设备还是文件系统”。管道和 TCP 的测试适合在需要评估 IPC 或本机网络栈时用。bw_pipe测的是管道吞吐lat_tcp测的是 TCP 往返延迟后者需要先启动一个服务端./lat_tcp -s # 先起服务端 ./lat_tcp -N 100 127.0.0.1 # 再测本机回环延迟-N 100是往返次数。注意本机回环测出来的延迟主要反映内核网络协议栈的开销不能当作物理网卡性能。要看真实网卡性能需要在一台机器上起服务端再从另一台机器发起测试否则回环路径会绕过网卡驱动结果偏乐观。4. 读懂报告关键指标、单位与对比方法4.1 报告里的三张表怎么读带宽表、延迟阶梯表、进程开销表make see输出的报告比较原始没有图表化第一次看容易懵。实际上报告就三部分信息第一部分是bw_mem和bw_file_rd的带宽数据一列是规模一列是带宽值这两类数字越大越好。第二部分是lat_mem_rd的延迟阶梯表这是我最先看的部分因为它直接反映缓存层级配置任何一个不正常的台阶都意味着缓存参数或内存控制器配置有问题。第三部分是进程开销数据lat_ctx、lat_fork、lat_exec、lat_sig这些微秒级数据它们没有绝对的好坏标准而是作为对比基线存在。报告里还有一个容易被忽略的细节每个测试项后面的输出顺序。make results是单线程跑完一个再跑下一个如果某个测试中途失败跳过后面所有数据都会保持对齐。所以先快速扫一遍报告里有没有空行或 NAN再逐项深入分析这个习惯能省很多排障时间。4.2 单位陷阱KB 还是 KiB、纳秒还是微秒、MB/s 的换算lmbench 的单位是工程师最容易掉进去的坑因为它的输出没有统一标注单位。带宽一律是十进制 MB/s即 1 MB 1,000,000 字节不是二进制的 MiB。如果你拿它和某些用 MiB/s 的工具对比数值上会有约 4.86% 的偏差这个误差足够掩盖真实性能差异了。延迟数据则要区分场景lat_mem_rd输出纳秒lat_ctx输出微秒同是延迟差了三个数量级不仔细看单位会把两个差距很大的数据误判成接近。下面这个表是我自己平时对照用的省得每次换算测试项输出字段默认单位说明bw_mem / bw_file_rd第二列MB/s十进制 MB非 MiBlat_mem_rd第二列ns纳秒除以 1000 得微秒lat_ctx第二列us微秒除以 1000 得毫秒lat_fork / lat_exec第二列us单次操作平均耗时另一个单位陷阱在bw_mem的第一列它显示的 size 是十进制表示比如输出1024.00表示 1024MB 而不是 1GiB。如果你习惯看二进制单位会觉得结果偏大但这只是表述差异不是测试错误。4.3 前后对比的严谨流程背景负载、CPU 频率、重复次数的控制拿 lmbench 做对比测试时最大的敌人不是工具本身而是环境不一致。很多人在跑“调优前 vs 调优后”时忽略了一个关键点现代 CPU 频率是动态变化的第一次跑测试时 CPU 可能还在低频节能状态几分钟后频率拉满同一台机器前后两次测出的内存带宽能差 20% 以上。这不是玄学是频率调节器在作怪。我习惯的严谨流程分三步。第一步先把 CPU 频率锁定sudo cpupower frequency-set -g performance这会把 CPU 调到最高频率且不降频消除频率变化带来的波动。第二步用taskset或numactl把测试绑到固定的核心上避免线程在不同核心间迁移污染缓存taskset -c 2-3 ./bw_mem 256M 1000 copy第三步是重复多次取中位数而不是平均值因为微基准对偶发中断很敏感平均值会被 outlier 拉偏中位数更能代表典型水平。整套流程走下来前后两次的差异能压缩到 5% 以内这时再讨论内核参数的优劣才有意义。5. 避坑手册新系统上编译与跑测最常见的 5 个问题5.1 编译报错 itimerval 未定义新版 glibc 与 2006 年代码的兼容现象在较新的 Linux 发行版上执行make OSlinux编译到一半报错常见的是timing.h或bench.h里itimerval、sigset_t这类型未定义运气好点能编完但链接时报O_LARGEFILE未声明。原因3.0 的源码默认不定义_GNU_SOURCE宏导致 glibc 头文件里一部分结构体没有被暴露。2006 年那个时代的 glibc 默认行为不同现在不行了。解决在src/lmbench.h文件最顶部追加三行强制开启 GNU 扩展# 编辑 src/lmbench.h在文件开头加入 #define _GNU_SOURCE #include sys/time.h #include sys/resource.h # 如果还报 O_LARGEFILE 未定义继续追加 #ifndef O_LARGEFILE #define O_LARGEFILE 0 #endif改完重新make clean make OSlinux基本能过。注意别去改系统头文件只改 lmbench 自己的头文件这样换一台机器重新编译时改动还容易移植。5.2 全部结果都是 0 或 NAN计时精度不够现象make results顺利跑完但查看结果时lat_fork、lat_sig、lat_ctx这些项的数字全是 0 或者 NAN带宽数据正常。原因这些微操作的耗时极短单次可能只有几微秒而 lmbench 内部用gettimeofday计时精度只有微秒级。重复次数不够时总耗时和计时误差在同一个数量级算出来的值就是 0 或 NAN。解决把重复次数调大命令行的最后一个参数或-N参数往上翻倍。比如lat_fork 100000、lat_ctx -N 100。如果是make results整跑改src/CONFIG文件里的LOOP变量把默认值调大。这里我踩过坑第一遍用默认配置跑出全 0以为二进制有问题差点把源码包删了重下后来发现只是次数太少属于血泪经验。5.3 OOM 崩溃内存参数配得比物理内存还大现象跑bw_mem或lat_mem_rd时进程直接被系统杀掉终端出现Killed查看dmesg能看到 OOM killer 的输出。原因bw_mem的copy操作需要同时分配两块缓冲区如果你传的 size 是 4096M实际内存占用是 8GB。make results自动跑的时候会读 CONFIG 里的MB配置如果这个值设得比物理内存一半还大系统内存耗尽就会触发 OOM。另外 32 位系统上单进程地址空间限制在 3GB 左右超过这个值malloc直接返回空指针lmbench 没有做严谨的判空行为不可预测。解决先free -g看物理内存bw_mem的 size 不要超过物理内存的一半lat_mem_rd的 size 不要超过物理内存总量。编辑src/CONFIG把MB改到一个安全值。嵌入式板子常见的是 512MB 内存MB 256是一个比较稳的配置。5.4 第一遍和第二遍差 20%CPU 频率与 NUMA 波动现象同一台机器、同一个命令连续跑三次bw_mem三次结果依次上升最大偏差超过 20%而且基本是“越跑越快”。原因这是两个因素叠加的结果。第一个是 CPU 频率调节器测试开始时 CPU 处于低功耗频率随着负载上来频率逐步拉满带宽自然一路走高。第二个是 NUMA 环境下内存页分配的位置不固定第一次测试的内存页可能落在远端内存控制器上后续测试页落在本地访问延迟差异明显。解决先锁定频率再测。多路服务器上还要在测试前用numactl --interleaveall把内存页均匀分布到各个 NUMA 节点避免某一次测试全部分配到远端节点。对比测试时记录每次的 CPU 频率信息cat /proc/cpuinfo | grep MHz确保频率一致再下结论。如果是在虚拟化环境里测宿主机的 CPU 调度会对结果产生不可控影响频率锁定没有意义这种场景下的数据只能做相对对比不能做绝对评价。5.5 交叉编译或容器里的结果不敢信哪些结果可信、哪些不可信现象拿 lmbench 在交叉编译环境里编出来的二进制拷到 ARM 板子上跑Memory Bandwidth 看起来正常但lat_ctx和lat_fork的数据和同型号其他板子差异极大有的甚至差好几倍。原因这是我在嵌入式项目里最有感触的一个坑。lmbench 的make results流程依赖 perl 脚本在“本机”直接执行编译产物交叉编译时这套流程会断掉所以常见做法是手动把单个二进制拷到板子上跑。但交叉编译时若工具链的优化参数和板子上原生 gcc 不一致微操作计时会被影响。另外有人图方便用 qemu-user 在 x86 机器上跑 arm 二进制qemu 的边端翻译会让 fork、信号这些系统调用的耗时完全失真。解决能原生编译就不要交叉编译必须交叉编译时对比结果只信bw_mem和lat_mem_rd这两类数据进程开销类数据不要用来跨设备对比。容器环境下跑 lmbench 可以测内核本身的变化但前提是容器没有被 cgroup 限制 CPU否则全部结果只和宿主机调度相关和你的内核改动无关。我一般在容器里只用它做“同一宿主机上的相对回归”绝不用容器数据对用户承诺绝对性能指标。6. 进阶用法把 lmbench 变成你的性能基线回归工具当你在一个项目里长期使用 lmbench 后最值得做的事是写一个小的回归脚本把几个关键指标固定下来每次调内核参数、换驱动版本、改 uboot 配置后都跑一遍数据留档。时间一长这些记录就是比任何压测报告都有说服力的性能历史。我这里给一个我自己的最小脚本它固定测三项内存带宽、内存延迟、上下文切换#!/bin/bash set -euo pipefail BASEresults/$(hostname).$(date %Y%m%d-%H%M) mkdir -p $BASE cd lmbench-3.0/src # 固定用 256M 测试拷贝带宽重复 3 次取中位数 for i in 1 2 3; do ./bw_mem 256M 1000 copy | awk {print $2} $BASE/bw_copy.txt done sort -n $BASE/bw_copy.txt | sed -n 2p $BASE/bw_copy.median # 内存延迟取 256M 那一行数据代表真正的内存延迟 ./lat_mem_rd 256M 128 64 1 | tail -1 $BASE/lat_mem_rd.txt # 上下文切换固定 16 进程 64KB 工作集 ./lat_ctx -P 16 -N 20 64 | tail -1 $BASE/lat_ctx.txt echo saved to $BASE cd -这个脚本的输出都在文件名带日期的目录里对比时直接把两次结果的对应文件做 diff。带宽中位数那一步用sort加sed取中位数比计算平均值更抗干扰。用的时候注意lat_mem_rd的tail -1取的是最大规模那一行如果你的机器内存特别大确保 256M 仍然远大于 LLClat_ctx尾部取的是最后一个工作集大小的数据行如果你要观察 cache 影响可以保留整份输出而不是只取一行。我自己的习惯是每次改动内核 cmdline 或者调完编译优化参数后都跑一份这个脚本跑之前先看一眼上一份数据再动手改配置。有一次调了一个内存相关的内核参数理论分析说带宽能提升 5%结果跑完发现延迟涨了 8%业务场景是延迟敏感型这波改动立刻被我回滚了。没有基线数据这种判断就全靠感觉了。这套方法不复杂但胜在规矩——性能优化这件事最怕的是没有可重复的测量基准。工具本身不会骗人骗人的往往是你的测试流程希望帮到你。本文还有配套的精品资源点击获取