多路复用场景下Perf计数器结果异常问题排查
问题背景与疑问
我使用Intel(R) Core(TM) i7-4720HQ CPU @ 2.60GHz(Haswell架构)处理器,运行Linux 4.15.0-20-generic内核。在系统相对空闲状态下,执行Perf命令监测offcore_response.all_data_rd.l3_miss.local_dram、offcore_response.all_code_rd.l3_miss.local_dram和mem_load_uops_retired.l3_miss三个计数器时,因PMU(性能监控单元)共享出现事件多路复用问题。
给第三个事件追加:D修饰符(强制独占计数器,避免多路复用)后,所有计数器数值大幅增大,且该现象仅在多路复用发生时出现。我还分别在空循环程序、内存密集型程序场景下进行了带:D和不带:D的多轮测试,发现不同场景下:D对各计数器的影响存在差异。
现提出以下疑问:
- 这种输出是否正常?
- 括号内的百分比数值是否有效?
- 如何避免计数器数值差异?
测试详情
1. 空闲系统测试
不带:D的命令及输出:
sudo perf stat -a -e offcore_response.all_data_rd.l3_miss.local_dram,offcore_response.all_code_rd.l3_miss.local_dram,mem_load_uops_retired.l3_miss ^C Performance counter stats for 'system wide': 229,579 offcore_response.all_data_rd.l3_miss.local_dram (99.72%) 489,151 offcore_response.all_code_rd.l3_miss.local_dram (99.77%) 110,543 mem_load_uops_retired.l3_miss (99.79%) 2.868899111 seconds time elapsed
带:D的命令及输出:
sudo perf stat -a -e offcore_response.all_data_rd.l3_miss.local_dram,offcore_response.all_code_rd.l3_miss.local_dram,mem_load_uops_retired.l3_miss:D ^C Performance counter stats for 'system wide': 539,397 offcore_response.all_data_rd.l3_miss.local_dram (68.71%) 890,344 offcore_response.all_code_rd.l3_miss.local_dram (68.67%) 193,555 mem_load_uops_retired.l3_miss:D 2.853095575 seconds time elapsed
2. 空循环程序测试
测试程序:
#include <iostream> using namespace std; int main() { for (unsigned long i = 0; i < 3 * 1e9; i++) ; return 0; }
测试命令:
执行7次以下两组命令:
sudo perf stat -e offcore_response.all_data_rd.l3_miss.local_dram,offcore_response.all_code_rd.l3_miss.local_dram,mem_load_uops_retired.l3_miss ./loop
和
sudo perf stat -e offcore_response.all_data_rd.l3_miss.local_dram,offcore_response.all_code_rd.l3_miss.local_dram,mem_load_uops_retired.l3_miss:D ./loop
测试结果:
all_data_rd受:D影响极小,all_code数值降低,load_uops_retired数值增大。
3. 内存密集型程序测试
测试程序:
#include <iostream> #include <cstring> #define DIM_SIZE 10000 using namespace std; char from [DIM_SIZE][DIM_SIZE], to [DIM_SIZE][DIM_SIZE]; int main() { for (char x = 'a'; x <= 'z'; x++) { for (int i = 0; i < DIM_SIZE; i++) memset(from[i], x, DIM_SIZE); for (int i = 0; i < DIM_SIZE; i++) memcpy(to[i], from[i], DIM_SIZE); } return 0; }
带:D的输出:
sudo perf stat -e offcore_response.all_data_rd.l3_miss.local_dram,offcore_response.all_code_rd.l3_miss.local_dram,mem_load_uops_retired.l3_miss:D ./loop Performance counter stats for './loop': 19,836,745 offcore_response.all_data_rd.l3_miss.local_dram (50.04%) 47,309 offcore_response.all_code_rd.l3_miss.local_dram (49.96%) 6,556,957 mem_load_uops_retired.l3_miss:D 0.592795335 seconds time elapsed
不带:D的输出:
sudo perf stat -e offcore_response.all_data_rd.l3_miss.local_dram,offcore_response.all_code_rd.l3_miss.local_dram,mem_load_uops_retired.l3_miss ./loop Performance counter stats for './loop': 18,742,540 offcore_response.all_data_rd.l3_miss.local_dram (66.64%) 76,854 offcore_response.all_code_rd.l3_miss.local_dram (66.64%) 6,967,919 mem_load_uops_retired.l3_miss (66.72%) 0.575828303 seconds time elapsed
测试结果:
此场景下带:D和不带:D的计数器数值几乎一致。
问题解答
1. 输出是否正常?
这种现象完全正常,核心原因是PMU多路复用机制和:D修饰符的作用逻辑:
- 不带
:D时,三个事件共享Haswell核的4个通用PMU计数器,perf会通过时间分片轮询统计不同事件,低频事件的触发次数容易被遗漏,导致统计值偏低; - 加上
:D后,mem_load_uops_retired.l3_miss会独占一个计数器,剩下两个事件只能共享剩余计数器,时间分片比例改变:原本三个事件分时间片,现在两个事件分剩余窗口,每个事件的统计占比提升,统计值更接近真实触发次数,所以数值增大; - 不同场景的差异源于事件触发频率:
- 空闲系统事件触发频率低,多路复用的遗漏效应更显著,加
:D后统计值提升幅度大; - 内存密集型场景中,
all_data_rd触发频率极高,perf会优先分配更多时间片给它,因此带不带:D的统计值差异极小; - 空循环场景中,
all_code(指令读取的L3 miss)触发频率低,:D独占计数器后,all_code的统计窗口占比下降,数值降低,而load_uops_retired.l3_miss因独占计数器,统计更完整,数值增大。
- 空闲系统事件触发频率低,多路复用的遗漏效应更显著,加
2. 括号内的百分比数值是否有效?
这些百分比完全有效,它代表该事件在统计周期内被PMU计数器实际监测的时间占比:
- 不带
:D时,三个事件共享计数器,空闲系统事件少,轮询切换开销可忽略,因此每个事件的监测占比接近100%; - 带
:D时,mem_load_uops_retired.l3_miss:D独占计数器,全程被监测(无百分比),剩下两个事件共享其他计数器,因此它们的监测占比降到约68%; - 这个百分比可以用来估算真实触发次数:真实值≈统计值 / 百分比,用来修正多路复用带来的统计偏差。
3. 如何避免计数器数值差异?
可以通过以下几种方式解决:
- 匹配事件数与硬件计数器数量:Haswell每个核有4个通用PMU计数器,若监测事件数≤4,不会发生多路复用,带不带
:D结果一致; - 合理使用
:D修饰符:对核心关键事件用:D独占计数器,确保统计完整,但需注意这会加重其他事件的多路复用,需根据需求权衡; - 拆分多次统计:把事件分成多组,每组事件数不超过CPU的PMU计数器数量,分别执行
perf stat后合并结果,保证每个事件都能被完整统计; - 使用
-A选项:多核系统中用-A(--per-core)让每个核独立统计事件,减少跨核多路复用的影响; - 升级内核或perf版本:较新的内核(5.x及以上)对PMU多路复用的调度更优化,能有效降低统计误差。
内容的提问来源于stack exchange,提问作者TheAhmad
相关产品推荐
相关产品推荐

