简介lmbench-3.0 是一款由 Larry McVoy 编写的多平台开源性能基准测试工具面向系统管理员、内核与驱动开发者以及硬件评测工程师用于评估系统综合性能尤其在内存带宽与内存延时测试方面表现突出。资源包共 225 个文件以 63 个 C 源码文件为核心配合 10 个头文件、5 个 Makefile 及 configure 等构建脚本另有大量 .8、.3、.1 手册页、tbl 数据表、ms 文档与 results、output 等测试结果样例压缩包约 508KB结构紧凑、模块清晰。目前已有 3658 人学习下载。借助该工具读者可完成内存拷贝、填充等带宽测试与读写查找延时测量并通过调整数据块大小、迭代次数及多线程参数适配不同硬件场景同时对照手册页与结果样例理解各测试项含义为性能诊断、算法选型和硬件验证提供可复现的数据支撑。1. lmbench-3.0为什么老工具在新内核上跑不出可信数字你在一台 64 核 ARM 服务器上跑lmbenchlat_ctx报出 0.38 微秒的进程上下文切换延迟比同代 x86 还快。换一台机器复现数字变成 2.1 微秒。同一份代码、同一个内核版本差异来自哪里大概率不是硬件而是lmbench-3.0的编译选项、CPU 亲和性设置和内核调度器状态没对齐。lmbench-3.0是 Carl Staelin 和 Larry McVoy 维护的经典系统性能微基准套件覆盖内存带宽、进程创建、上下文切换、网络 socket、文件读写等底层指标。它不测应用吞吐只测操作系统和硬件的“裸能力”。做内核调优、选型对比、虚拟化性能验证的人离不开它。但它的默认配置面向二十年前的单核 SMP 机器直接在现代多核、NUMA、cgroup 环境下跑数字基本是玄学。这篇笔记按“先立住原理、再动手复现、最后排坑”的顺序把lmbench-3.0从编译到出报告整条链路拆开让你拿到的数字能写进对比表格而不是扔进垃圾桶。2. 把 lmbench-3.0 跑起来编译、配置与最小验证2.1 源码获取与编译前的环境检查lmbench-3.0的源码通常以lmbench-3.0.tar.gz形式分发解压后目录结构是src/、bin/、scripts/、results/。编译前先确认三件事目标机器有gcc和make/usr/include下有sched.h和numaif.hNUMA 相关内核开启了CONFIG_HZ可读。常见做法是直接make但默认CFLAGS是-O对微基准来说优化级别不够稳定我一般会显式覆盖。# 解压并进入源码目录 tar xzf lmbench-3.0.tar.gz cd lmbench-3.0 # 查看当前 Makefile 里的默认编译选项 grep -n CFLAGS src/Makefile | head -20 # 用显式优化级别和调试符号重新编译 make clean make CFLAGS-O2 -g -fno-omit-frame-pointer \ LDFLAGS-lpthread -lm \ -j$(nproc)这段命令的逻辑是先清理旧对象文件避免不同优化级别混链-O2是微基准的常用平衡点-O0会让循环开销盖过被测系统调用-O3可能把被测代码向量化导致语义偏移-fno-omit-frame-pointer是为了perf采样时能拿到完整调用栈。LDFLAGS里-lpthread必须加lmbench-3.0的lat_ctx和bw_mem在多线程模式下依赖 pthread。编译完成后bin/下会出现lat_ctx、lat_proc、bw_mem、lat_syscall等可执行文件用file bin/lat_ctx确认是当前架构的 ELF。2.2 用 scripts/ 下的配置脚本生成第一份结果lmbench-3.0不推荐手工逐个跑bin/下的程序而是用scripts/里的config-run和results流程。最小验证路径是先跑scripts/config-run生成CONFIG文件再跑scripts/results输出汇总。但config-run会交互式提问批量环境下用scripts/config-run的非交互模式。# 进入 scripts 目录准备非交互配置 cd scripts # 生成一个最小配置只测上下文切换和内存带宽 cat myconfig EOF LMBENCH_FAST1 LMBENCH_SLOW0 ENOUGH1000 TIMING_O1 SYNC1 EOF # 用该配置跑一轮结果写入 ../results/ 下 ./config-run -c myconfig ./results -c myconfigLMBENCH_FAST1表示只跑快速测试集适合做冒烟验证ENOUGH1000是每个测试的最小迭代次数低于这个值统计噪声会明显变大TIMING_O1打开计时开销校准lmbench-3.0会先测一个空循环来扣除计时器本身的开销。跑完后results/下会生成summary.out和若干*.out明细文件。第一次跑不要追求数字好看先确认summary.out里每项都有值、没有NaN或0.00。2.3 读懂 summary.out 里的三列数字summary.out的格式是“测试名 指标 值 单位”。以lat_ctx为例输出类似测试项指标典型值现代 x86单位lat_ctx2p/0K0.9microsecondslat_ctx8p/64K2.4microsecondsbw_mem64M rd8500MB/slat_procforkexec420microsecondslat_ctx的2p/0K表示 2 个进程、0KB 数据量下的上下文切换延迟8p/64K是 8 进程、每进程 64KB 工作集工作集超过 L1 后延迟会跳升。bw_mem的64M rd是 64MB 数组的读带宽。这些数字必须和ENOUGH、CPU 频率、NUMA 节点绑定一起看单独一个值没有意义。我一般会在summary.out旁边记下lscpu的型号、nproc、cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor否则过两天就忘了当时机器什么状态。3. 让数字可信CPU 亲和性、NUMA 与内核状态控制3.1 用 taskset 和 numactl 固定测试进程lmbench-3.0默认不绑核调度器可能把测试进程在核心间迁移导致lat_ctx的 cache 命中率剧烈波动。常见做法是用taskset把整个测试绑到固定核心NUMA 机器上再用numactl --cpunodebind0 --membind0把内存也绑到同一节点。# 绑到 CPU 2-3内存绑到 node 0跑 lat_ctx taskset -c 2-3 numactl --cpunodebind0 --membind0 \ bin/lat_ctx -N 5 -P 2 8 64 # 参数说明 # -N 5 重复 5 轮取统计 # -P 2 2 个进程 # 8 进程数从 2 到 8 递增 # 64 工作集大小 64KB-N 5让lmbench-3.0输出 5 轮结果你可以看 min/median/max 的离散程度如果 max 比 min 大 3 倍以上说明绑核没生效或后台有干扰。-P 2是并行进程数8和64是进程数上限和工作集。绑核后lat_ctx的 2p/0K 通常能稳定在 0.8-1.2 微秒x86不绑核可能飘到 2 微秒以上。3.2 关闭频率调节和中断合并现代 CPU 的cpufreqgovernor 默认是powersave或schedutil频率随负载浮动微基准跑出来的延迟包含升频时间。跑之前把 governor 设成performance并关闭irqbalance或把网卡中断绑到非测试核心。# 查看当前 governor cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor | sort -u # 临时设为 performance需要 root for c in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do echo performance $c done # 关闭 irqbalance如果存在 systemctl stop irqbalance 2/dev/null || trueperformancegovernor 让 CPU 始终跑在最高非睿频频率避免lat_ctx测到升频延迟。irqbalance会把中断随机分配到核心如果测试核心被网卡中断打断lat_ctx的 max 值会异常高。生产环境不能长期关irqbalance但做基准对比的窗口期内必须关。跑完记得恢复原 governor否则机器功耗和温度会明显上升。3.3 用 perf 验证测试期间没有意外事件lmbench-3.0只给结果不给过程数字异常时你不知道是 cache miss 还是调度延迟。用perf stat包一层看context-switches、cache-misses、cpu-migrations三个指标。# 用 perf stat 包住 lat_ctx看底层事件 perf stat -e context-switches,cache-misses,cpu-migrations \ taskset -c 2-3 bin/lat_ctx -N 3 -P 2 4 0如果cpu-migrations不为 0说明绑核失败如果cache-misses占比超过 5%lat_ctx的 0K 工作集结果不可信因为进程切换时 L1 被污染。context-switches的数量应该和-N、-P的乘积量级一致差太多说明lmbench-3.0的计时逻辑被信号打断。这一步是排坑的后悔药数字对不上时先看perf stat再怀疑硬件。4. 避坑与排查lmbench-3.0 最常见的 5 个翻车现场4.1 现象lat_ctx 结果全是 0.00 或 NaN原因ENOUGH设得太小或者TIMING_O没开计时器分辨率不够。lmbench-3.0在迭代次数不足时直接输出 0不报错。解决把ENOUGH提到 10000 以上确认TIMING_O1并检查bin/lat_ctx是否有执行权限。如果仍然为 0用strace -c bin/lat_ctx看是否在clock_gettime上失败。4.2 现象bw_mem 带宽比理论值高 30%原因编译器把bw_mem的读循环优化掉了实际没读内存。-O2下 GCC 可能识别出循环结果未被使用而删除。解决编译时加-fno-tree-dce或把bw_mem的CFLAGS单独设为-O1。更稳妥的做法是跑完后用perf stat -e cache-misses验证如果 cache-misses 接近 0 而带宽很高就是被优化了。4.3 现象lat_proc forkexec 延迟随运行次数递增原因系统dentry和inode缓存被测试进程反复创建销毁内存碎片增加fork的页表复制变慢。解决每轮测试前sync echo 3 /proc/sys/vm/drop_caches并在summary.out里记录这是冷缓存还是热缓存结果。对比不同机器时必须统一缓存状态否则数字没有可比性。4.4 现象NUMA 机器上 bw_mem 带宽只有单节点一半原因numactl --membind没生效内存分配跨节点带宽被 interconnect 限制。解决用numastat -p $(pgrep bw_mem)确认进程的内存分布如果other_node列不为 0说明绑定失败。检查numactl版本和内核CONFIG_NUMA是否开启必要时在bw_mem源码里加mbind调用。4.5 现象同一台机器两次跑结果差 20% 以上原因CPU 睿频状态、后台systemd定时任务、或lmbench-3.0的随机种子导致工作集地址对齐不同。解决跑之前systemctl stop掉非必要服务用cpupower frequency-set -g performance锁频并在myconfig里固定SYNC1让每轮测试前同步。如果仍然波动把-N提到 10 以上取中位数不要用单次值。5. 进阶把 lmbench-3.0 接入自动化对比流水线5.1 用脚本批量跑多配置并生成对比表单次跑lmbench-3.0只能得到一个快照做选型或调优对比时需要跑多组配置。我一般写一个 bash 脚本把 governor、绑核、ENOUGH作为变量循环跑完解析summary.out生成 CSV。#!/bin/bash # run_lmbench_matrix.sh set -euo pipefail GOVERNORS(performance schedutil) CPUSETS(2-3 4-5) OUTDIRresults/matrix mkdir -p $OUTDIR for gov in ${GOVERNORS[]}; do for cpus in ${CPUSETS[]}; do tag${gov}_cpu${cpus//-/_} echo running $tag for c in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do echo $gov $c done taskset -c $cpus bin/lat_ctx -N 5 -P 2 8 64 \ $OUTDIR/lat_ctx_$tag.out 21 taskset -c $cpus bin/bw_mem -N 5 -P 2 64m rd \ $OUTDIR/bw_mem_$tag.out 21 done done # 解析并汇总 python3 - PY import glob, re, csv rows [] for f in glob.glob(results/matrix/*.out): name f.split(/)[-1].replace(.out, ) with open(f) as fh: for line in fh: m re.match(r(\S)\s(\S)\s([\d.]), line) if m: rows.append([name, m.group(1), m.group(2), m.group(3)]) with open(results/matrix/summary.csv, w, newline) as fh: w csv.writer(fh) w.writerow([config, test, metric, value]) w.writerows(rows) PY脚本的逻辑是外层循环 governor内层循环 CPU 集合每个组合跑lat_ctx和bw_mem输出到独立文件最后用 Python 正则解析每行汇总成 CSV。set -euo pipefail保证任何一步失败就停避免脏数据混入。tag里的//-/_是把2-3转成2_3防止文件名里的连字符被误解析。跑完后summary.csv可以直接拖进表格软件做透视对比。5.2 用中位数和离散度判断结果是否可用CSV 里每个配置有多轮值不要直接取平均。我习惯用 Python 算中位数和(max-min)/median的离散度离散度超过 15% 的配置直接标记为不可用重新跑。import pandas as pd df pd.read_csv(results/matrix/summary.csv) pivot df.groupby([config, test, metric])[value].agg( medianmedian, minmin, maxmax ).reset_index() pivot[spread] (pivot[max] - pivot[min]) / pivot[median] pivot[usable] pivot[spread] 0.15 print(pivot.to_string(indexFalse))median比mean抗离群值spread是离散度usable是布尔标记。如果某个配置的spread超过 0.15先查perf stat的cpu-migrations再查 governor 是否真的生效。这一步是自动化流水线的质量门禁没有它后面所有对比都是自欺欺人。5.3 一个我踩过的坑别在容器里跑 lmbench-3.0有一次在 Docker 容器里跑lmbench-3.0lat_ctx结果比宿主机高 40%。原因是容器共享内核cgroup的 CPU 配额和cpu.shares让测试进程被限流lat_ctx测到的是调度器排队时间而不是上下文切换本身。后来改成--privileged --cpuset-cpus2-3并关掉cpu.cfs_quota_us数字才和宿主机对齐。如果你必须在容器里跑至少确认cpu.stat里的nr_throttled为 0否则结果只能内部纵向对比不能跨环境横比。跑lmbench-3.0这些年我最大的习惯是任何数字进报告之前先问自己“绑核了吗、锁频了吗、缓存状态一致吗”。这三个问题答不上来数字再漂亮也不写。希望帮到你。本文还有配套的精品资源点击获取
