ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

cachebench 缓存带宽测试:编译、NUMA绑核与可复现避坑

cachebench 缓存带宽测试:编译、NUMA绑核与可复现避坑 简介LLCbench 是一套面向 Linux 平台的低层表征基准测试工具集整合了 MPBench、CacheBench 与 BLASBench 三种测试方法此包取其中 CacheBench 部分用于衡量处理器各级缓存L1/L2/LLC的读写带宽与访问延迟适合体系结构研究、服务器选型对比或性能调优的开发者与高校师生使用。压缩包共 81 个文件、约 83KB含 cachebench.c 等 C 源码、Makefile 构建脚本、llcbench.h 头文件与 user.def 参数定义以及 sys.linux-mpich、sys.solaris、sys.ppc 等十余份平台配置模板配合 doc 目录下的 html/tex 说明文档和 ps、gp 绘图脚本可完成从编译、跑分到结果出图的完整流程。已有 873 人学习体量轻便解压后按对应平台模板即可编译验证缓存带宽与延迟表现。1. cachebench 这类跑缓存性能的工具很多人第一次接触是从一份 llcbench.tar.gz 开始的包里几个用 C 写的 Linux 开源工具专做缓存层次和处理器性能测试。上周有人拿一台二手工作站来问为什么同一份包在他的机器上编译完cachebench 跑出来的 L3 带宽比标称值低将近一个数量级。我上去看scaling_governor还是powersave测试进程在 NUMA 节点间来回迁移工作集上限只设到 8 MB连 L3 都没越过去。工具本身没问题问题出在装完就开跑。缓存测试对环境的敏感度比一般压测高得多频率、绑核、大页、编译器优化等级任何一项没配好出来的数字看着像结果实际是不可复现的噪声。这篇装的东西不复杂一份源码包、一个 GCC、几个内核开关但顺序和参数得对。2. 装前先量体llcbench 的依赖清单与 GCC 工具链选型2.1 先用 lscpu 和 getconf 摸清这台机器的缓存层级cachebench 的横轴是工作集大小纵轴是访存带宽曲线上的台阶就是缓存边界。你要是不知道这台机器的 L1d、L2、LLC 各多大、LLC 是每个核心独享还是整颗 CPU 共享那看到拐点也不知道对不对看到没拐点也不知道是程序问题还是参数问题。所以装之前先花两分钟把硬件底账抄下来存档留作后面判读的依据。# 一次看清拓扑、缓存和 NUMA 节点数 lscpu | grep -Ei model name|socket|core|thread|numa|cache # getconf 读的是内核导出的值和 lscpu 交叉验证 getconf -a | grep -iE CACHE_SIZE|CACHE_LINE|CACHE_ASSOC # sysfs 里的 index 编号才是权威index0L1d, index2L2, index3LLC for i in /sys/devices/system/cpu/cpu0/cache/index*; do echo $i cat $i/level $i/type $i/size $i/shared_cpu_list done第一条命令里的socket和numa决定后面怎么绑核第二条的LEVEL1_DCACHE_SIZE、LEVEL3_CACHE_SIZE单位是字节用来给 cachebench 的-m参数定上限提供参考。最关键是第三条shared_cpu_list会告诉你 LLC 被哪些逻辑核共享比如输出0-15说明这 16 个逻辑核共享同一个 LLC那测试时把进程绑到这 16 个核之外的任何一个访存就要走跨片路径带宽数字会明显掉一截。这一步做完本机的缓存地图就有了顺手记进一个hardware.txt以后换机器、换内核版本都有对照。2.2 编译依赖GCC、make、MPI、BLAS 哪些是必须的llcbench 这个包里通常不止一个可执行程序cachebench 是纯 C 加标准库只需要 GCC 和 make要是你还想跑 mpbench就得有 MPI 的编译包装器mpiccblasbench 则要链接一个 BLAS 实现。很多人一上来把整条 MPI 栈、整套 BLAS 全拉下来装完发现只是测个缓存白白多出几十个包和一堆环境变量冲突。按需装才是对的。组件版本要求Debian/Ubuntu 包名RHEL/Rocky 包名GCC7 以上913 都稳build-essentialgcc、glibc-develmake4.xmakemakenumactl2.xnumactlnumactlcpupower需跟内核版本对齐linux-tools-generickernel-toolsperf同上linux-tools-commonperfMPI可选OpenMPI 4.xlibopenmpi-dev、openmpi-binopenmpi-develOpenBLAS可选0.3.xlibopenblas-devopenblas-devel# 最小可用集只跑 cachebench 的话这一行就够 sudo apt-get update sudo apt-get install -y build-essential make numactl linux-tools-common linux-tools-generic # 确实要跑 mpbench / blasbench 时再补这两行 sudo apt-get install -y libopenmpi-dev openmpi-bin sudo apt-get install -y libopenblas-devRHEL 系把上面的包名换成表里右列即可注意dnf install openmpi-devel装完mpicc落在/usr/lib64/openmpi/bin下默认不在 PATH 里得用module load mpi/openmpi-x86_64或者手动把那个目录加进去否则which mpicc一无所获。cpupower和perf一定要跟当前内核同一个版本号版本错位时 cpupower 会报Invalid argument之类的错perf 则可能直接提示找不到符号这类问题九成是版本没对齐而不是权限。2.3 解包与目录规划源码别扔在 /root 下源码目录和结果目录要分开而且最好不要放在家目录。原因很实际测试前要清 page cache、要关 swap跑完还会生成一堆 CSV 和日志混在/root里三个月后你自己都分不清哪份数据是哪台机器的。我一般固定用/usr/local/src放源码/opt/bench放二进制和结果。# 先看包里的顶层结构确认解出来是单目录还是散开的 tar -tzf llcbench.tar.gz | head -30 # 解到固定位置源码保持只读心态不在这里改 sudo mkdir -p /usr/local/src /opt/bench/bin sudo tar -xzf llcbench.tar.gz -C /usr/local/src cd /usr/local/src/llcbench ls -F第一条命令只是预览确认压缩包里是有一个顶层目录还是文件直接铺开铺开的情况要加--one-top-level解到子目录里否则几十个文件撒进/usr/local/src很难收拾。第二条解完之后ls -F看看有哪些子目录通常能看到和几个模块同名的目录外加 Makefile 和说明文件。这时候先别急着make把gcc --version、uname -r、lscpu的摘要一起写进/opt/bench/README.env以后复现结果全靠这份记录。3. 编译 cachebenchMakefile 目标选择与 CFLAGS 取舍3.1 顶层 Makefile 的平台目标怎么读这类老牌 benchmark 包的顶层 Makefile 是手写的平台目标按操作系统命名Linux 下常见的是linux、linux-x86_64这两种写法年份不同、维护者不同目标名会变。直接make大多数情况会因为没指定目标而只编译第一个规则出来的东西未必是你想要的。先读 Makefile 头部比盲试快得多。cd /usr/local/src/llcbench # 看前 80 行平台目标一般都在最上面 sed -n 1,80p Makefile # 看看有哪些 target 可选有 help 目标就省事了 make help 2/dev/null || grep -nE ^[a-zA-Z0-9_.-]: Makefile | head -40sed那一行是为了看清变量定义重点找CC、CFLAGS、ARCH这几个它们决定编译时实际传了什么。第二条是退路老包不一定有help目标直接 grep 出所有规则名更实在。找到目标后按下面的方式传参用命令行的CC/CFLAGS覆盖掉 Makefile 里的默认值这是最不容易出错的做法。# 用命令行变量覆盖避免去改源码里的 Makefile make CCgcc CFLAGS-O2 -fno-omit-frame-pointer -D_GNU_SOURCE linux-x86_64 # 换了目标或者改了 CFLAGS一定先 clean否则 .o 会复用 make clean make CCgcc CFLAGS-O2 -fno-omit-frame-pointer linux-x86_64CCgcc显式指定编译器是为了防止某些发行版把cc软链到 clang编译能过但生成的代码特性不一样。-O2是有意选的-O3会让编译器做更激进的循环展开和向量化访问步长被改写测出来的就不是你写的那条访存路径了。-fno-omit-frame-pointer是给后面 perf 采样留后路少了它调用栈会断。clean那一步是血泪经验Makefile 的依赖关系经常漏写头文件改了 CFLAGS 不 clean链接进去的还是旧目标文件你会以为参数没生效。3.2 单模块编译绕过顶层 Makefile 更省事顶层目标对不上你的发行版时别跟 Makefile 较劲直接进模块目录编译单文件。cachebench 是单文件程序一条 gcc 就能出二进制比调试 Makefile 快。先ls确认源文件名别照抄我这里的名字。cd /usr/local/src/llcbench/cachebench ls *.c # -O2 保持循环结构-static 避免目标机 glibc 版本不一致-lm 是数学库 gcc -O2 -fno-omit-frame-pointer -D_GNU_SOURCE \ -o cachebench cachebench.c -lm -static # 编完立刻验证二进制能不能跑、依赖是否干净 file cachebench ldd cachebench || echo static, no dynamic deps ./cachebench -h 21 | head -30参数逐个说清-D_GNU_SOURCE是为了让clock_gettime、getopt_long这些接口在严格标准模式下也可见缺了它某些 glibc 版本会报隐式声明。-lm一定要放在源文件后面链接器从左到右解析放前面会报undefined reference to sqrt。-static不是必须的但如果编译机和测试机不是同一台强烈建议加上动态链接的 glibc 版本差异会让二进制在目标机上直接起不来。最后那条-h是必须做的不同年份的包cachebench 的命令行开关不完全一样先看它自己打印的用法后面所有命令都按这个输出调整别硬套别人的参数。3.3 mpbench 与 blasbench 的附加编译参数这两个模块属于“有就顺手编没有也不影响主流程”。mpbench 走 MPI编译器和链接器都要换成mpiccblasbench 要链接 BLAS库名在不同发行版上不一样这是最容易卡住的地方。# mpbench用 mpicc 而不是 gcc头文件和链接参数它自己带 cd /usr/local/src/llcbench/mpbench ls *.c mpicc -O2 -o mpbench mpbench.c -lm # blasbench库名按发行版二选一找不到就查一下 cd /usr/local/src/llcbench/blasbench gcc -O2 -o blasbench blasbench.c -lopenblas -lm # Debian 系也可能是 -lblasRHEL 上 OpenBLAS 常在 /usr/lib64/openblasmpicc本质上是个包装脚本会把 include 路径和-lmpi都补上所以不要自作聪明改成gcc -lmpiOpenMPI 的库路径经常不在默认搜索路径里。blasbench 编译失败基本只有一种报错undefined reference to dgemm_说明 BLAS 没链上先ldconfig -p | grep -i blas看到底装了哪个实现再决定用-lopenblas还是-lblas如果库在非标准路径补一个-L/usr/lib64/openblas。编完把三个二进制统一收进/opt/bench/bin源码目录就不再动了。install -m 0755 cachebench mpbench blasbench /opt/bench/bin/ 2/dev/null ls -l /opt/bench/bininstall -m 0755比cp更直接权限一步到位2/dev/null是容忍某个模块没编出来的情况比如你没装 MPImpbench 不存在不影响另外两个。4. 让数字可复现频率、NUMA 与 THP 的三处配置4.1 频率与 C-state别让 CPU 在测试中自己降频默认的powersave调速器会让 CPU 频率跟着负载浮动缓存带宽和核心频率强相关频率一动同一段代码跑两次能差出三成。这不是玄学是电源管理在正常工作只是和测量的目标冲突。# 看当前策略和支持的策略 cpupower frequency-info | grep -E driver|governor|available cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor # 全部核心切到 performance sudo cpupower frequency-set -g performance # 关掉睿频intel_pstate 和 acpi-cpufreq 路径不同两个都试 cat /sys/devices/system/cpu/intel_pstate/no_turbo 2/dev/null echo 1 | sudo tee /sys/devices/system/cpu/intel_pstate/no_turbo 2/dev/null echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost 2/dev/null # 顺带把 C-state 限制到 C1避免深睡唤醒延迟污染小工作集的测量 sudo cpupower idle-set -D 1-g performance是把下限顶到上限代价是功耗和温度测试机上无所谓。关睿频这一步经常被忽略睿频一开单核跑分时频率会冲到标称之上测出来的 L1 带宽比理论值还高看着很爽但完全不可比。idle-set -D 1限制的是空闲状态深度工作集只有几十 KB 的时候CPU 大部分时间在等待缓存命中深 C-state 的进出延迟会体现在结果里。这几条改的都是运行时状态重启即失效所以不用怕改坏但也意味着每次重启后要重新执行。4.2 NUMA 与绑核单线程跑分最怕迁移双路机器上跨 NUMA 节点访存的带宽可能只有本地的一半多一点。cachebench 是单线程程序如果内核调度器在测试过程中把它从这个节点挪到那个节点前半段和后半段测的根本不是同一块内存曲线会出现莫名其妙的抖动。# 先看清节点布局和每个节点的本地内存大小 numactl -H # 绑到 0 号节点的 CPU 和内存两边都锁死 numactl --cpunodebind0 --membind0 /opt/bench/bin/cachebench -b -m 512 # 没有 numactl 时至少把进程钉在某个核上 taskset -c 2 /opt/bench/bin/cachebench -b -m 512--cpunodebind管 CPU--membind管内存分配两个都要写。只写前者内存仍可能从远端节点分配测出来的带宽照样偏低。taskset -c 2是更轻量的替代只保证不迁移不保证内存在本地适合已经在 BIOS 里关掉节点交织的机器。判断有没有绑对跑的时候另开一个窗口执行ps -o psr,comm -p $(pgrep cachebench)看PSR那一列是不是一直不变。4.3 大页、swap 与 page cache把内存侧的噪声压掉透明大页会让同一份工作集有时候落在 4K 页、有时候落在 2M 页页表项数量和 TLB 命中率完全不同这就是同一台机器两次结果不一致的常见来源。测缓存的时候要的是确定性的页行为宁可关掉。# 关闭 THP让所有分配都走 4K 页行为可预测 echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag # 关掉 NUMA balancing它会周期性扫描并迁移页面 echo 0 | sudo tee /proc/sys/kernel/numa_balancing # 测试期间不需要 swap避免偶发换出拖慢大工作集那一段 sudo swapoff -a # 跑之前清一次 page cache保证大数组是真正的冷内存 sync echo 3 | sudo tee /proc/sys/vm/drop_cachesdefrag那一项和enabled要一起关只关一个的情况下内核仍可能为了凑大页去做内存整理表现为测试中途出现几百毫秒的停顿。drop_caches写 3 表示同时清 page cache、dentries 和 inodes需要 root且只在测试开始前执行一次不要在循环里反复清那样会把测量变成 IO 测试。4.4 一份可复用的测试前脚本与还原脚本上面这些零散命令每次手敲三遍必出错。写成两个脚本一个负责进入测试态一个负责还原跑完一定执行还原否则这台机器后面跑什么都偏慢。#!/usr/bin/env bash # /opt/bench/prep.sh —— 进入测试态需要 root set -euo pipefail for f in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do [ -w $f ] echo performance $f # 逐核设置别指望一条命令全生效 done echo never /sys/kernel/mm/transparent_hugepage/enabled echo never /sys/kernel/mm/transparent_hugepage/defrag echo 0 /proc/sys/kernel/numa_balancing # 停止页面自动迁移 swapoff -a || true # 已关时返回非零用 || true 兜住 sync echo 3 /proc/sys/vm/drop_caches echo [prep] done at $(date -Is)set -euo pipefail让脚本在任一步失败时立刻停下避免只配了一半还以为配好了。逐核写scaling_governor而不是只用 cpupower是因为某些内核上 cpupower 只改 policy 对象个别核心会被漏掉。|| true只加在swapoff上因为它对已关闭的 swap 会返回非零这是预期行为不该中断脚本。#!/usr/bin/env bash # /opt/bench/restore.sh —— 还原跑完立刻执行 set -uo pipefail for f in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do [ -w $f ] echo schedutil $f # 按发行版默认策略改ondemand 也行 done echo madvise /sys/kernel/mm/transparent_hugepage/enabled echo 1 /proc/sys/kernel/numa_balancing echo [restore] done at $(date -Is)还原脚本里没去动 swap 和睿频因为这两项在多数生产机上本来就不该由测试脚本随意改动手工确认更稳妥。schedutil在较新内核上是默认策略老内核用ondemand改之前先看一眼/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors里有什么别写一个不存在的策略名进去。5. 避坑排查llcbench 编译与运行的五类翻车5.1 编译期目标不存在和头文件找不到现象一make linux-x86_64报No rule to make target。原因这份包的 Makefile 年代较早平台目标命名和你猜的不一样可能只写了linux也可能要求通过变量传架构。解决grep -nE ^[a-z].*: Makefile把所有规则列出来照着用实在没有匹配的放弃顶层 Makefile进模块目录按 3.2 节的方式单文件编译两分钟的事别在这里耗。现象二fatal error: mpi.h: No such file or directory但明明装了 OpenMPI。原因openmpi-devel在 RHEL 系上装到/usr/lib64/openmpi/头文件和mpicc都不进默认搜索路径。解决用mpicc编译而不是gcc或者先module load mpi/openmpi-x86_64Debian 系一般没这个问题因为libopenmpi-dev会把头文件放进/usr/include。5.2 运行期段错误、全零输出和虚高带宽现象三二进制一跑就Segmentation fault编译时毫无警告。原因老代码里对long和指针宽度的假设在 64 位下不成立或者工作集上限-m给得太大、超过了容器或 cgroup 的内存限制被 OOM killer 干掉。解决先dmesg | tail -20看有没有 oom 记录有就把-m降到物理内存的六成以内没有就加-g重编一版用gdb ./cachebench跑出栈定位到具体行。现象四输出表里所有尺寸的带宽都是同一个数量级或者干脆全是 0。原因多半是编译优化过头。-O3加上-marchnative时编译器可能识别出循环里的访存模式没有副作用直接把整段消掉计时器测到的是一个空循环于是输出一个巨大且平坦的值。解决退回-O2去掉-marchnativemake clean后重编。判断是不是被优化掉objdump -d cachebench | grep -c mfence之类的指令计数只是个参考最直接的办法是看跑的时候top里 CPU 占用是不是接近 100%低于这个数基本可以确定循环没真正执行。5.3 结果期曲线没拐点、两次差三成现象五带宽曲线是一条平滑下降的直线看不到 L1/L2/L3 的台阶。原因三条可能工作集上限-m太小没越过 LLCTHP 没关导致页行为不一致把台阶抹平了或者进程每次都重新分配内存、测的其实是页错误开销。解决把-m设成 LLC 容量的 816 倍比如 LLC 32 MB 就给-m 512确认第 4 章的脚本执行过跑之前清一次 page cache。现象六同一台机器、同一份二进制两次跑差 30% 以上。原因频率没锁、进程被调度到别的 NUMA 节点、或者机器上还有别的进程在抢 LLC。解决按 4.1 和 4.2 节锁频绑核跑之前ps aux --sort-%cpu | head确认没有别的重负载两次结果差异用标准差衡量绑核绑对了通常能压到 3% 以内压不下去就说明还有别的东西在动。6. 装完怎么验证跑通三组数字对上了才算装完装完不等于跑通。判断标准很简单cachebench 的输出、perf stat的计数、硬件的理论值这三组能互相解释才算这套环境搭成功了。只看 cachebench 那一张表很容易被虚高的数字骗过去。先跑一遍基准把输出存下来# -b 跑完整扫描-m 给到 LLC 的 8~16 倍-i 提高迭代次数降低抖动 /opt/bench/bin/cachebench -b -m 512 | tee /opt/bench/run-$(date %Y%m%d-%H%M).log看这张表只做三件事。第一找台阶从几 KB 到几十 KB 这一段应该是最高带宽越过 L1 掉一档越过 L2 再掉一档越过 LLC 掉到内存水平台阶位置要和你 2.1 节抄下来的缓存尺寸对得上差一个数量级说明参数或环境有问题。第二看读写差异同尺寸下读带宽通常高于写如果写反而更高多半是写路径被编译器优化成了非临时存储回去检查 CFLAGS。第三看尾部是否收敛工作集超过物理内存的六成以后曲线应该趋于平缓还在往下掉说明已经在换页了。再用 perf 交叉验证一次确认测的就是缓存行为# perf_event_paranoid 太严会拒绝非 root 采样先放开到 1 echo 1 | sudo tee /proc/sys/kernel/perf_event_paranoid # 只跑一个中等工作集看 LLC 命中率是否随尺寸变化 sudo perf stat -e cycles,instructions,cache-references,cache-misses,LLC-loads,LLC-load-misses \ /opt/bench/bin/cachebench -b -m 512cache-misses除以cache-references得到的失效率应该随着工作集增大单调上升LLC-load-misses在越过 LLC 之后会明显抬升。如果失效率从头到尾是平的说明 perf 采样到的和 cachebench 实际访问的不是同一段内存回头检查numactl有没有写错节点。最后拿理论值对一下量级单通道 DDR 的实测带宽通常在十几 GB/s 这个量级L1 能到上百 GB/sL3 介于两者之间实测值低于理论峰值三成以内都算正常差一个数量级就别急着写报告。观察点正常表现异常时先查什么台阶位置与 lscpu 缓存尺寸一致-m上限、THP 是否关读 vs 写读带宽更高CFLAGS 是否-O3尾部收敛超过内存六成后走平是否触发换页、-m过大perf 失效率随工作集单调上升绑核、NUMA 节点从那以后我每次把 llcbench 解到一台新机器上都强制先跑lscpu抄底账、执行一遍 prep 脚本锁频绑核、再用 perf 对一次失效率三步缺一步就不记录数字。这个习惯救过我两次一次是发现机器 BIOS 里睿频根本没关另一次是发现容器只给了 1 GB 内存-m 512直接撞上了 cgroup 上限。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表