perf mem获取内存分配信息的机制与perf kmem完全不同:它不监听内核的分配事件,而是通过 CPU 硬件性能计数器,直接采样程序实际发生的每一次内存“访问”(加载/存储)。
核心机制:硬件采样,而非软件跟踪
perf kmem依赖内核预埋的 tracepoint(如kmalloc),属于软件事件跟踪。而perf mem利用的是CPU 内置的 PMU(性能监控单元),属于硬件采样。
采样对象:程序执行的每一次内存Load(加载)或Store(存储)指令。
关键技术:通常依赖 Intel 的PEBS(精确事件采样)或 AMD 的IBS。当内存访问事件发生时,硬件会直接记录下指令地址、数据地址、访问延迟、缓存层级等详细信息。
数据流向:硬件计数器采样 → 环形缓冲区 →
perf收集并解析 →perf.data文件。
perf mem能获取哪些“内存信息”?
由于是硬件直接采样,它能捕获到软件 tracepoint 无法提供的运行时微架构信息:
访问延迟(Latency):这是核心信息。它记录的是“使用延迟”(Use-Latency),即从发起内存访问到 CPU 真正需要用到该数据时的总耗时,包含了内存子系统的延迟以及流水线排队等额外延迟。这比单纯的 DRAM 延迟更能反映程序的实际卡顿情况。
缓存层级(Memory Level):明确指出这次访问是命中L1、L2、L3 缓存,还是落到了本地 RAM或远端 NUMA 节点。
TLB 访问状态:记录 TLB 是否命中,这对分析页表遍历开销很关键。
数据地址:记录被访问的数据地址(Data Address),而不仅仅是指令地址。结合符号解析,可以知道具体是哪个变量或数据结构导致了缓存未命中。
内存锁状态(Memory Locked):标识该访问是否涉及
lock前缀指令。
perf mem分析 CPU 内存访问的核心思路是:利用 CPU 硬件采样单元,捕获每一次内存访问的“结果”和“代价”,然后将这些原始数据转化为定位缓存瓶颈的线索。
采样什么:硬件捕获的内存访问细节
perf mem不记录每一次内存操作,而是通过PEBS (Intel)或IBS (AMD)等硬件机制,对内存访问事件进行采样。每次采样都会捕获一组关键信息:
访问类型 (op):是加载 (load) 还是存储 (store)。
数据来源 (mem):这次访问最终由哪一层提供数据,例如L1、L2、L3 缓存、本地 RAM 或远端 NUMA 节点。
访问延迟 (latency):获取数据所需的处理器周期数。需要注意的是,在 Intel 平台上,这通常是“使用延迟” (use-latency),它包含了内存子系统的延迟以及流水线排队等待的时间,更能反映程序实际感受到的卡顿。
监听结果 (snoop):在多核环境下,是否命中另一个核心的缓存行(如
HitM,表示命中一个“已修改”的缓存行),这是识别伪共享的关键线索。数据地址 (data address):被访问的具体内存地址(或符号),这是定位“哪个变量/数据结构访问慢”的基础。
分析什么:从原始数据到性能瓶颈
拿到采样数据后,perf mem通过聚合和排序,将微观的访问细节转化为宏观的性能视图。
1. 定位“慢”的函数或数据
最直接的用法是按符号或内存访问层级进行汇总排序。例如,使用perf mem report --sort=symbol,mem,可以快速看出哪个函数导致了最多的L3 miss或RAM hit,从而锁定内存访问热点。
2. 识别缓存未命中的来源
报告会清晰展示访问的命中层级分布。一个典型的输出会显示不同内存层级的开销占比,例如:
31.53% LFB or LFB hit 29.70% L1 or L1 hit 23.03% L3 or L3 hit 12.91% Local RAM or RAM hit
这能让你一眼看出瓶颈在缓存内还是已经穿透到了主存。
3. 诊断伪共享 (False Sharing)
伪共享是多线程性能的隐形杀手。当perf mem报告在snoop列显示HitM(命中已修改缓存行)时,就明确指示了缓存行在不同核心间的争用。这意味着两个线程可能修改了同一缓存行上的不同变量,导致缓存行来回“弹跳”,严重拖慢速度。
4. 量化真实开销:基于权重而非样本数
perf mem计算“开销 (Overhead)”的方式很关键:它使用样本的权重 (weight) 而非样本数量 (period)来计算。延迟越高的访问,其权重越大。
这意味着,即使一个函数产生的内存访问样本很少,但如果每次访问的延迟都极高(例如每次都访问 RAM),它在perf mem report中的开销占比也会很高,从而被凸显出来。
与kmem的本质区别
| 维度 | perf kmem | perf mem |
|---|---|---|
| 信息源 | 内核Tracepoint(软件插桩) | CPUPMU(硬件计数器) |
| 关注点 | 分配(谁申请了内存、是否泄漏/碎片) | 访问(谁在读/写内存、快不快) |
| 典型用途 | 排查内核内存泄漏、Slab 碎片 | 排查用户态程序的缓存未命中、内存延迟热点 |
简单来说,kmem回答“内存是怎么被分配出去的”,而mem回答“内存是怎么被访问的,访问效率如何”。
实战流程示例
结合以上机制,一个典型的内存瓶颈定位流程如下:
采样:
perf mem record -o mem.data -- ./your_program查看缓存层级分布:
perf mem report -i mem.data --sort=mem定位到具体函数:如果发现
L3 miss或RAM hit很高,用perf mem report -i mem.data --sort=symbol,mem找出是哪个函数导致的。深入代码行:对于热点函数,可以用
perf annotate --symbol=hot_function -i mem.data定位到具体哪一行代码的加载指令(如mov)在等待内存-14。检查伪共享:如果程序是多线程的,在报告中留意
snoop列出现的HitM,并考虑使用perf c2c工具进行更深入的缓存行争用分析。