Linux perf 事件调度算法

本文深入解析了Linux perf子系统中事件调度算法,包括事件组的概念、NMIwatchdog的作用、以及不同处理器架构下的事件调度策略。实例分析展示了事件多路复用、计数器分配和调度算法的实际应用。

1. 背景

任何人了解现代 Intel 处理器上的硬件性能监控的第一件事就是每个逻辑核心有三个固定功能计数器和四个通用计数器。只要有足够的计数器用于需要同时测量的所有事件,为每个事件分配一个计数器是可行的。但是,如果事件多于计数器,则会在不同时间间隔测量不同事件的情况下发生多路复用。在 perf stat 工具中,如果在输出右侧看到一列百分比,则表示已使用多路复用来测量给定事件。但是你有没有看到一个输出说:「WTF,为什么会有多路复用?」

这里有一些 perf stat 命令,可以尝试看看输出是否符合预期。早于 Skylake 的微架构上使用的事件和事件代码与 Skylake 及更高版本上的事件代码相同;只是某些事件的名称略有不同。尝试在启用或禁用超线程的不同微架构上运行这些命令。还可以在具有相同微体系架构但内核版本不同的系统上进行尝试。多路复用是否按预期发生?

是否在某些事件旁边看到 <not counted> or <not supported> 而不是实际事件计数。

注:在本文中,事件计数为数值并不重要;重要的是事件如何被调度,每个事件的确切含义也不重要。

Sandy Bridge up to and including Broadwell
------------------------------------------
perf stat -e l1d_pend_miss.pending,cycle_activity.stalls_l1d_pending dd if=/dev/zero of=/dev/null count=1000000
perf stat -e l1d_pend_miss.pending,cycle_activity.stalls_l1d_pending:D dd if=/dev/zero of=/dev/null count=1000000
perf stat -e '{l1d_pend_miss.pending,faults}',cycle_activity.stalls_l1d_pending:D,mem_uops_retired.all_loads dd if=/dev/zero of=/dev/null count=1000000
perf stat -e mem_load_uops_retired.l1_hit,mem_load_uops_retired.l1_miss,mem_load_uops_retired.l2_hit dd if=/dev/zero of=/dev/null count=1000000
perf stat -e mem_load_uops_retired.l1_hit,mem_load_uops_retired.l1_miss,mem_load_uops_retired.hit_lfb,mem_load_uops_retired.l2_hit,mem_load_uops_retired.l3_hit dd if=/dev/zero of=/dev/null count=1000000
Haswell and Broadwell
---------------------
perf stat -e dtlb_load_misses.walk_completed,dtlb_load_misses.walk_completed_4k,dtlb_store_misses.walk_completed,dtlb_store_misses.walk_completed_4k,itlb_misses.walk_completed,itlb_misses.walk_completed_4k dd if=/dev/zero of=/dev/null count=1000000
perf stat -e '{dtlb_load_misses.walk_completed,dtlb_load_misses.walk_completed_4k,dtlb_store_misses.walk_completed,dtlb_store_misses.walk_completed_4k,itlb_misses.walk_completed,itlb_misses.walk_completed_4k}' dd if=/dev/zero of=/dev/null count=1000000
perf stat -e instructions,dtlb_load_misses.walk_completed,dtlb_load_misses.walk_completed_4k,dtlb_store_misses.walk_completed,dtlb_store_misses.walk_completed_4k,itlb_misses.walk_completed,itlb_misses.walk_completed_4k sleep 1
Skylake up to and including Sunny Cove
--------------------------------------
perf stat -e l1d_pend_miss.pending,cycle_activity.stalls_l1d_miss dd if=/dev/zero of=/dev/null count=1000000
perf stat -e l1d_pend_miss.pending,cycle_activity.stalls_l1d_miss:D dd if=/dev/zero of=/dev/null count=1000000
perf stat -e '{l1d_pend_miss.pending,faults}',cycle_activity.stalls_l1d_miss:D,mem_inst_retired.all_loads dd if=/dev/zero of=/dev/null count=1000000
perf stat -e mem_load_retired.l1_hit,mem_load_retired.l1_miss,mem_load_retired.l2_hit dd if=/dev/zero of=/dev/null count=1000000
perf stat -e mem_load_retired.l1_hit,mem_load_retired.l1_miss,mem_load_retired.fb_hit,mem_load_retired.l2_hit,mem_load_retired.l3_hit dd if=/dev/zero of=/dev/null count=1000000
perf stat -e dtlb_load_misses.walk_completed,dtlb_load_misses.walk_completed_4k,dtlb_store_misses.walk_completed,dtlb_store_misses.walk_completed_4k,itlb_misses.walk_completed,itlb_misses.walk_completed_4k dd if=/dev/zero of=/dev/null count=1000000
perf stat -e '{dtlb_load_misses.walk_completed,dtlb_load_misses.walk_completed_4k,dtlb_store_misses.walk_completed,dtlb_store_misses.walk_completed_4k,itlb_misses.walk_completed,itlb_misses.walk_completed_4k}' dd if=/dev/zero of=/dev/null count=1000000
perf stat -e instructions,dtlb_load_misses.walk_completed,dtlb_load_misses.walk_completed_4k,dtlb_store_misses.walk_completed,dtlb_store_misses.walk_completed_4k,itlb_misses.walk_completed,itlb_misses.walk_completed_4k sleep 1

在其中一些命令中,使用了 ‘{}’ 语法,这意味着括号中的所有事件应该做一组一起调度,它们之间没有任何多路复用,但整个组仍然可以与其他组复用。笑脸修饰符(冒号后跟 D)意味着事件(或事件组)应该被固定,即它不受多路复用的影响,并且应该始终被测量。perf list 手册中讨论了这两个特性。

注:dd 工具将指定数量的块从一个文件复制到另一个文件,它做什么其实并不重要,此处需要小程序用于测量。

一旦了解了 perf event 调度算法,就能够理解上述每个命令的所有可能输出以及具有更复杂事件集的任何其他命令。所以本文将首先讨论算法,然后最后解释输出。目前没有关于 AMD 处理器的示例。但是,如果有人制作了一些 AMD 示例,可以在评论中补充,使这篇文章涵盖的范围更全。

注:作者事先为没有制作任何文章的章节而道歉,这将使阅读这篇文章更容易一下。

2. Introduction

Linux perf 子系统由一个称为 perf_event 的内核组件组成,它通过 perf_event_open 系统调用向用户空间公开,以及一组用户空间工具,可用于对一个或多个感兴趣的事件进行计数或捕获样本。由操作系统计数的事件称为软件事件。操作系统可以在内存中分配一个计数器,并在事件发生时递增计数器。例如,页面错误的数量可以在页面错误处理程序中进行计数。由于页面错误是由 x86 处理器上的硬件引发的,因此它们也可以由处理器计算。但是,大多数事件只能计入硬件或软件,而不能同时计入两者。例如,只有操作系统可以分别计算次要和主要页面错误。另一方面, L1 数据缓存访问和分支错误预测等事件只能由硬件计算。这些事件和许多其他事情都很重要,因为它们会对性能和/或能耗产生重大的影响。

操作系统可以为每个软件创建一个计数器,从而使事件和计数器一一对应。这是可能的,因为操作系统事件计数器的数量实际上是无限的。每个事件都可以简单地在为其分配的计数器上进行计数。为了使事件有效地用于性能监控和分析,事件计数过程本身应该对性能没有影响或可以忽略不计。与页面错误相比,在页面错误上增加计数器的成本可以忽略不计。不幸的是,这种为每个事件分配一个计数器的方法不适用于硬件事件,因为大多数硬件事件的成本通常不超过几百个周期。因此,每当事件发生执行加载—修改—存储操作以维护事件的计数器是禁止的。相反,硬件事件时使用硬件中的少量计数器和相关逻辑来计数的。

从 perf 子系统的角度来看,用于维护一组事件的计数的硬件资源统称为性能监控单元(PMU)。一个处理器可以提供多个 PMU,其中每个 PMU 都有自己独立的资源并支持不同的事件集。自 Nehalem 以来的英特尔处理器为每个逻辑核心提供至少一个 PMU 和一个非核心 PMU。PMU 中的计数器数量通常远小于该 PMU 支持的事件总数。通常,每个计数器都有一个关联的控制寄存器,可以对其进行编程以指定要在该计数器寄存器上计数的事件。一些称为固定功能计数器的计数器只能用于对单个事件进行计数,但它们仍可能提供有限的可编程性,例如是对同一事件的内核模式还是用户模式的发生进行计数。类似地,某些事件可能对哪些计数器可用于对其进行计数有限制。给定一组要计数的硬件事件,需要为每个事件分配一个计数器,以便满足事件的约束。由于冲突(例如,两个事件需要相同的计数器),可能会发生没有将事件有效分配给计数器的情况。这种情况可以使用事件多路复用来解决,其中对 PMU 进行编程,以便可以在不同的时间点同时对不同的事件进行计数。为给定事件集找到有效分配的任务称为事件调度问题。软件事件和计数器之间的一一对应是这个问题对于软件事件来说是微不足道的。

一些工具让用户自行决定是否将硬件事件正确分配给计数器。考虑例如 likwid-perfctr,其中用户必须通过附加 :PMcn 或 :FIXcn 来为需要测量的每个事件指定计数器,它们表示通过(GP)计数器和固定功能(FP)计数器,分别到事件名称。但是,likwid-perfctr 知道事件限制(嗯,大部分限制),它会通知指定的时间表是否违反了一个或多个事件的限制。但是,它不会告诉是否以及如何安排活动。likwid-perfctr 默认使用 msr-safe 或 msr 内核模块对计数器进行编程。另一方面,Linux perf 实现了一种事件调度算法,它没有提供任何选项(通过 perf_event_open 接口)来指定事件调度。本文将讨论 Linux perf 事件调度算法在所有 Linux 内核版本中使用,直到 5.3-rc7,这是最新的。

3. Event Groups

perf 中的调度单元不是单个事件,而是一个事件组,其中可能包含一个或多个事件(可能在不同的 PMU 上)。事件组的概念对于确保在同一时间段内同时测量一组数学上相关的事件很有用。例如,L1 缓存未命中次数不应大于 L2 缓存访问次数。否则,事件可能会被多路复用,并且它们的测量值

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值