Linux下perf可直接读取真实L3缓存命中率,需用uncore_cbox_XX:LLC_MISSES/LLC_REFERENCES事件计算,而非估算;perf mem record能精确定位高延迟访存函数;false sharing需结合snoop stall指标识别;NUMA优化须确保线程与内存同die。

Linux 下用 perf 直接读取 L3 cache 命中率(非估算)
Linux perf 是唯一能在生产环境稳定获取真实 L3 cache 命中率的工具,它直接读取 CPU 的硬件性能计数器(PMU),不是靠采样估算。关键在于用对事件:L3 缓存未命中必须用 uncore_cbox_00:LLC_MISSES 这类 uncore 事件,而非 LLC-load-misses(那是内核模拟的、不准)。
实操建议:
- 先确认 CPU 支持:运行
perf list | grep -i "llc\|uncore",看到类似uncore_cbox_00:LLC_MISSES和uncore_cbox_00:LLC_REFERENCES才能继续 - 绑定到具体 core 测:用
taskset -c 0 perf stat -e uncore_cbox_00:LLC_MISSES,uncore_cbox_00:LLC_REFERENCES -I 1000 -- sleep 5,每秒输出一次原始计数 - L3 命中率 =
(LLC_REFERENCES - LLC_MISSES) / LLC_REFERENCES,注意不是用cache-misses除以cache-references—— 那是 L1/L2 混合指标,完全不反映 L3 - 多 socket 系统要分别测每个 socket 的 uncore cbox(如
uncore_cbox_01:LLC_MISSES),编号对应物理 socket,不能只看 00
用 perf mem record 定位访存延迟热点函数
perf mem record 是目前唯一能将高延迟访存归因到具体函数+指令地址的工具,它依赖 Intel PEBS(Precise Event-Based Sampling)机制,精度远超 perf record -e cycles,instructions。
常见错误现象:运行 perf mem record -e mem-loads -- ./myapp 后,perf mem report 显示大量 [unknown] 或地址无符号——这是没开 kernel debuginfo 或未编译带 -g。
立即学习“C++免费学习笔记(深入)”;
实操建议:
- 必须用
perf mem record -e mem-loads,mem-stores -d -- ./myapp(-d开启数据地址采样) - 确保二进制含调试信息:
g++ -O2 -g -march=native编译,且系统安装了对应 kernel 的kernel-debuginfo - 关注 report 中
MEM LOAD INSNS:LATENCY列 > 200 cycles 的行,这才是真正的 DRAM 延迟瓶颈; - 如果 latency 分布集中在 30–60 cycles,大概率是 L3 未命中但被同 socket 其他 core 的 L3 命中(即 snoop hit),此时需检查 NUMA 绑定是否合理
规避 false sharing 导致的 L3 命中率虚高但性能差
很多程序 perf 显示 L3 命中率 >95%,但实际吞吐上不去——问题常出在 false sharing:多个线程写同一 cache line 不同字段,触发 cache line 在 core 间反复无效化(invalidation),虽然数据始终在 L3,但每次写都需广播 snoop,等效延迟飙升。
实操判断方法:
- 查
perf stat -e uncore_cbox_00:LLC_SNOOP_STALLS,uncore_cbox_00:LLC_SNOOP_HITS:若LLC_SNOOP_STALLS占比 >10%,基本可定性为 false sharing - 用
perf script -F comm,pid,tid,ip,sym,dso | grep -E "(write|store)"结合地址对齐分析,找相邻 64 字节内多个线程频繁写的变量 - 修复不是简单加
alignas(64),而是按访问模式重排结构体:把只读字段和各线程独占写的字段分开,避免混在同一 cache line - Clang/LLVM 的
-fsanitize=thread能在运行时捕获 false sharing,但仅限 debug 构建,不能用于压测
NUMA-aware 内存分配 + L3 亲和性绑定的实际效果边界
用 numactl --membind=0 --cpunodebind=0 绑定进程后,L3 命中率未必提升——现代 Intel Xeon 的 L3 是 inclusive 且 per-die,但跨 die 访问仍需 QPI/UPI 路由,延迟翻倍。真正起作用的是让线程和它访问的内存落在同一 die 内,而非仅同一 NUMA node。
实操要点:
-
numactl --hardware看清楚 die 和 node 的映射关系,有些双路系统一个 node 包含两个 die(比如 AMD EPYC),这时--membind=0可能跨 die - 分配内存时用
libnuma的numa_alloc_onnode(),但注意:malloc 默认不走 numa 接口,必须显式调用或用LD_PRELOAD=./libnuma_interpose.so拦截 - CPU 绑定用
pthread_setaffinity_np()比taskset更精确,可指定到 logical CPU ID,避免 OS 调度漂移 - 即使全绑定正确,若 workload 有强跨核共享(如 lock contention、ring buffer 生产消费),L3 效率仍会坍塌——这时得换 lock-free 数据结构,不是调优能解决的
真实瓶颈往往藏在 uncore 事件和内存访问模式交叉分析里,单看 L3 hit ratio 数字容易误判。尤其要注意 snoop stall 和跨 die traffic 这两类指标,它们才是延迟的最终裁判。


















