文章目录

  • 1. 背景
  • 2. Introduction
  • 3. Event Groups
  • 4. The NMI Watchdog
  • 5. The Scheduling Algorithm
  • 6. Event Constraints Calculation
  • 7. Examples
  • 8. 碎碎念
  • 9. 参考资料

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 缓存访问次数。否则,事件可能会被多路复用,并且它们的测量值将不再具有可比性,从而使分析更加困难。

一个事件组由一个领先事件和零个或多个同级事件组成。当调用 perf_event_open 将事件添加到由 cpu、pid 和 type 参数指定的事件上下文时,该事件是添加到新组还是现有的组由 group_fd 参数确定。

  • 如果 group_fd 为 -1,则该事件被视为领先事件,并为该事件创建一个新组。事件被添加到组中,新组被添加到事件上下文中。
  • 否则,如果 group_fd 指定了有效的组文件描述符,则该事件被视为同级事件,因此将添加到由 group_fd 指定的同一组中。

每次成功调用 perf_event_open 都会将一个事件添加到指定的事件上下文中。事件上下文中有两个事件组列表,一个用于固定事件组,另一个用于灵活事件组。通过在添加组的前导事件时将 perf_event_attr 参数的 pinned 字段设置为 1 来创建固定事件组,这告诉 perf 由于多路复用,该事件组永远不应被调度。

当即将创建组的主导事件时,如果是硬件事件,则进行检查以确保事件的约束使得可以调度事件;至少有一个计数器可以对事件进行计数。这是在 perf 子系统的 CPU 供应商特定层(Interl 或 AMD)完成的,因为正是在这一层定义了约束。如果此检查失败,则 perf_event_open 返回 EINVAL 并且不会创建事件组。将同级事件添加到现有组时,将执行检查以确保新事件与组中的新事件位于同一 PMU 上的所有现有事件一起可以在该 PMU 上调度。当每个 PMU 上的组的最后一个事件添加到组时,其他 PMU 上的事件组的可调度性已经执行。新事件仅影响其 PMU 上的计划,因此这是唯一需要执行的验证检查。只有领导者事件和启用的兄弟事件参与此检查;不考虑使用 perf_event_attr 的禁用位设置为 1 创建的同级事件。稍后将与调度算法一起讨论事件组验证算法。如果未找到包含新事件的有效时间表(例如,事件数已大于可用的 GP 和 FP 计数器的总数),则 perf_event_open 返回 EINVAL 并且不会将事件添加到组中。还有许多其他检查,此处只提到了与事件调度最相关的那些。

4. The NMI Watchdog

NMI watchdog 是一种检测硬锁定并可能相应地执行一些补救措施的机制。默认情况下,它在系统中的每个在线逻辑核心上启用。但是,恶意通过引导时和运行时内核参数禁用它。具有以下属性(仅显示相关属性)的硬件事件被创建并添加到启用了 NMI watchdog 的每个逻辑内核的事件上下文中。

static struct perf_event_attr wd_hw_attr = {.type       = PERF_TYPE_HARDWARE,.config       = PERF_COUNT_HW_CPU_CYCLES,.pinned     = 1,
};

事件 PERF_COUNT_HW_CPU_CYCLES 在 Intel 和 AMD 处理器上实现为未暂停的核心周期事件,可以在其中一个 FP 计数器和任何 GP 计数器上调度。如果在启动时启用了 NMI watchdog,则很可能会为其分配 FP 计数器(稍后会澄清)。由于事件被固定,计数器将基本上无法用于其他潜在用途。如果 NMI watchdog 被禁用然后动态启用,仍认为它会获得 FP 计数器,因为它是在固定组列表中安排的第一个固定组。我认为 GP 计数器可能被分配给 NMI watchdog 的唯一情况是它在启动时被禁用,然后稍后启用。无论那种方式,在讨论调度算法时,请记住其中一个计数器可能为每个逻辑内核上的 NMI watchdog 保留。有人提议使用基于芯片组的计时器来实现 NMI watchdog,而不是永久消耗宝贵的性能计数器,但它尚未在内核中被接受。

5. The Scheduling Algorithm

在讨论调度算法本身之前,首先需要解决「何时」和「什么」的问题。也就是说,何时执行事件调度以及调度要考虑哪些事件(即算法的输入)。在以下情况下启动事件调度操作:

  • 在从一个任务到不同任务的上下文切换时,可能有一个或多个启用事件与下一个任务相关联。需要考虑调度的事件集合可能不同,因此必须构建新的调度。请注意,与上一个任务关联的事件需要被禁用。但是,只有这些特定事件被禁用;所有其他事件将继续启用并使用相同的计划,除非下一个任务添加更多事件。

  • 当手动启动 event、a group leader 或者其它时,手动启用事件有五种方式:

    • 使用 PERF_EVENT_IOC_ENABLE 标志对 perf_event 文件描述符执行 ioctl 系统调用
    • 对附加到调用者事件上下文的事件执行 prctl 系统调用 PR_TASK_PERF_EVENTS_ENABLE 标志
    • 使用 PERF_EVENT_IOC_REFRESH 标志对 perf_event 文件描述符执行 ioctl 系统调用
    • 对 exec 的调用启用所有使用 enable_on_exec 设置为 1 并与当前任务相关联的事件
    • 对带有 PERF_EVENT_IOC_MODIFY_ATTRIBUTES 标志的 perf_event 文件描述符执行 ioctl 系统调用

    事件调度只针对新启用的事件所属的 PMU 启动。

  • 调用 perf_event_open 并将 perf_event_attr 参数的 disabled 字段设置为 0,这表明新事件已启用并且必须立即在其 PMU 上安排。如果事件与任务相关联(即 pid 参数为非负数),则仅当任务当前在至少一个逻辑核心上运行时才会触发事件调度。这是通过向运行任务的每个内核发送中断来实现的。否则,事件将安排在任务的下一次上下文切换上。

  • 当事件多路复用的定时器中断到期时,当每个 PMU 的逻辑内核上启用的事件不能全部同时调度时,这是需要的。

  • 当 ptrace 设置或修改硬件断点时,会在硬件断点 PMU 上启用断点事件。

  • 当 NMI watchdog 在启动时或运行时在逻辑内核上启用时,它会在该内核上添加一个新的硬件事件,如前所述。当对 cgroup 进行以下更改之一时:

    • 新任务迁移到 cgroup
    • 更改 cgroup 类型
    • 启用或禁用 cgroup 的子系统
  • 当 /sys/devices/cpu/allow_tsx_force_abort 在 Intel Skylake(和关闭的衍生产品)处理器上更改时。这会更改核心 PMU 的 GP 计数器之一的可用性,这需要在所有逻辑核心上重新安排该 PMU上的所有启用事件。将在本文后面讨论事件约束。

禁用事件也会更改有资格在该事件的 PMU 上调度的事件集。特别是,可以避免在禁用该事件的情况下进行多路复用。但是,单独禁用事件不会立即触发事件调度。相反,事件(如果是 group leader,则包括所有兄弟事件)将简单地停止测量。稍后,当上述任何一种情况发生时,可能会构建一个更好的时间表。

所有事件组都保持在称为事件上下文的数据结构中。每个任务都有一个事件上下文,每个 PMU 都有一个每个 CPU(即逻辑内核)的事件上下文。根据用于创建组引导事件的 pid、cpu 和 perf_event_attr.type 参数,该事件将添加到指定 CPU 上的任务的事件上下文或 PMU 的事件上下文中。特别是,如果 pid 为非负数,则将事件添加到指定任务的的事件上下文中。本例中的 cpu 参数仅可用于测量任务在指定 cpu 上运行时的事件。否则,cpu 参数必须指定一个有效的 cpu(以及 type 参数中的一个 PMU),以便可以将事件添加到其事件上下文中。同一组中的所有事件必须具有相同的 pid 和 cpu。但是,事件类型可能不同,但整个组最终都处于单个事件上下文中。当存在不同类型的事件时如何确定事件上下文的确切细节超出了本文的范围。

任何事件的另一个与调度相关的属性是它是固定的还是灵活的。对于同一事件组中的所有事件,这两个属性必须相同(否则 perf_event_open 返回 EINVAL)。总体而言,任何事件组都属于以下四个类别之一:

  • Per-cpu 和 pinned:事件组应在启用时在指定的逻辑核上进行测量。如果由于没有可用的计数器而无法调度事件组,则它会进入错误状态。稍后将对此进行讨论。固定的事件组不受多路复用的影响。固定事件组的概念仅适用于包括硬件事件的组,由于 PMU 限制,可能并非所有启用的事件都可以同时处于活动状态。
  • Per-task 和 pinned:当事件组被启用并且拥有其事件上下文的任务在任何逻辑核心上运行时(或者如果 cpu 参数有效,则在特定的逻辑核心上运行),都应该测量事件组。
  • Per-cpu 和 flexible:事件组应在启用时在指定的逻辑核心上进行测量,但如果需要可以多路复用。
  • Per-task 和 flexible:事件组应该在拥有其事件上下文的任务正在运行的逻辑核心上进行测量,但如果需要,它可以被多路复用。

事件组调度在不同的逻辑核上独立执行。当事件调度在逻辑核心上开始时,只有那些应该在该逻辑核心上测量的事件才有资格(基于 pid 和 cpu)进行调度。因此,只有来自该逻辑核上 PMU 的事件上下文的事件组和当前在逻辑核上运行的任务的事件上下文被考虑用于调度。此外,如果 goup leader 是禁用状态,则其所有兄弟事件也有效地处于禁用状态,因此它们都不是可以调度的。事件组按以下顺序安排: per-cpu 和 pinned、per-task 和 pinned、per-cpu 和 flexible、per-task 和 flexible。pinned 优于 flexible,以使他们有更好的机会被安排。通过一个接一个地安排每个组来逐步构建事件时间表。如果无法安排 pinned group,则它会从启用状态转换为不考虑安排的错误状态。对于处于错误状态的组而言,唯一可能的状态转换时启用状态,这会触发如前所述的调度。如果无法调度 flexible group,则在逻辑核心上启用多路复用。perf 将尝试按顺序安排所有符合条件的组。如果它看到一个未能调度的组,则所有包含至少一个硬件事件的后续组都不会被考虑进行调度;只有软件事件的组总是会成功安排。

现在我将讨论如何将单个组中的事件安排在尚未分配给早期组的 PMU 资源上(在 PMU 上安排的第一个组具有所有可用的 PMU 资源)。此外,组中的每个事件都由事件所属的 PMU 处理,调度算法的确切细节主要取决于 PMU。此处将只讨论 x86 核心 PMU 中使用的算法,因为这是最常见的一种,其他的要么相似,要么更简单。启用的 x86 核心 PMU 事件的算法工作如下:

  1. 计算当前正在安排的组中每个事件的约束。来自先前调度组的事件的约束已经从该算法的先前执行中确定,但是具有动态约束的事件的约束必须重新计算。将在本文后面讨论事件约束计算。

  2. 计算每个事件的约束权重。事件约束被表示为一个位向量,其中包含 PMU 中每个计数器的一位。例如,具有 3 个 FP 计数器和 4 个 GP 计数器的 PMU 的向量由 7 个位组成,其中一个置位位表示可以在相应的计数器上测量事件。设置的位数称为约束的权重。

  3. 确定 GP 计数器的使用限制。此步骤在内核 4.1 - rc1 中实现,仅在某些情况下是必需的(取决于内核版本),稍后将讨论

  4. 对于当前正在安排的组中的每个事件以及从具有最小约束权重(即,最受约束)的事件开始到最大约束权重(即,最小约束)的所有早期组,执行以下操作:

    • 如果可以在 FP 计数器上安排事件,并且有一个未使用的匹配 FP 计数器,则将事件分配给该计数器。在内核 3.3 - rc1 中,FP 计数器优于 GP 计数器。在较旧的内核版本中,可能会发生可以在 FP 计数器上测量的事件被分配一个 GP 计数器,这可能会导致不必要的多路复用。
    • 否则,如果可以在 GP 计数器上安排事件,并且有一个未使用的匹配 GP 计数器,则将事件分配给第一个此类计数器。
    • 如果事件已分配给计数器,则事件已成功安排。如果事件的约束被标记为重叠(稍后给出定义)并且当前保存的调度器状态数量少于 2 ,则保存当前的调度器状态。重叠事件约束目前仅存在于 AMD 处理器上。在内核 3.3 - rc1 及其更高版本中实现了对重叠计数器的调度支持。
    • 如果事件没有被分配任何计数器,检查是否有一个保持的调度器状态。如果有,通过将在保存状态之间分配的所有计数器标记为未使用来回滚到最后保持的状态,尝试为保存该状态是调度的事件分配不同的计数器,并重做调度后来的事件。如果没有,则调度程序未能为事件分配计数器,这意味着无法调度包含该事件的整个事件组。
  5. 在成功的情况下,所有分配的计数器都会被相应地编程并启用,从而使事件处于活动状态。在失败的情况下,pinned 组和 flexible 组将被区别对待。对于 pinned 组,调度失败的 pinned 组将转换为错误状态,这使得它在重新启用之前没有资格在未来进行调度。对于 flexible 组,在调度失败的组的事件上下文上启用多路复用。此外,更早的组(已成功)的调度被激活。

总之,调度程序从逻辑核心的 PMU 的事件上下文和当前运行的任务中迭代所有启用的 pinned 和 flexible 事件组,直到它到达调度失败的组,之后只调度纯软件组。对于每个组,为了最大限度地使用可用计数器,算法从组中约束最大的事件开始到约束最小的事件。在内核版本 2.6.34-rc1 之前,没有考虑约束的权重,因此调度事件的顺序是基于事件创建的顺序,这是不可取的,因为用户必须考虑事件的顺序。每个 PMU 和每个逻辑核心都独立完成调度(尽管作为调度单元的组可能包含来自不同 PMU 的事件)。

组验证使用相同的算法完成,只是不执行步骤 3 和 5。在组验证中,仅考虑正在验证的组来检查组本身是否可调度。否则,该组基本上是无效的。

这里值得注意的是,PMU 资源的可用性可能不仅取决于迄今为止在同一核心上安排的事件,还取决于其他核心。这是因为某些 PMU 或某些类型的 PMU 资源在包的同一内核或所有内核的线程之间共享,在这种情况下,它们的可用性可能会根据在其他内核上测量的事件而改变。例如,所有内核共享 Intel 处理器上的非核心 PMU 和 AMD 处理器上的北桥 PMU。在 Nehalem 和 Westmere 上,内核外响应事件的 MSR 寄存器在内核的两个线程之间共享。

如果无法调度一个或多个 flexible 组,则启用基于 hrtimer 基础结构 CLOCK_MONOTONIC 类型的计数器来执行多路复用。此计数器是为每个 PMU 逻辑内核创建的。在内核 3.11 - rc1 之前,多路复用是在调度程序中断上执行的,调度程序也使用该中断在任务之间切换。这使得中断周期可以独立于调度程序时钟进行配置。多路复用周期可以通过名为 /sys/devices/xxx/perf_event_mux_interval_ms 的 sysfs 条目配置,其中 xxx 是 PMU 的名称,例如 cpu。请注意,这只能针对每个 PMU 进行配置,而不能针对每个逻辑内核进行配置。多路复用周期涉及性能开销和测量精度之间的权衡。

当多路复用中断发生时,中断处理程序首先检查当前 PMU 上下文和/或当前任务上下文是否启用了多路复用。此检查是有必要的,因为事件时间表可能会动态变化,因此需要多路复用。在启用它的每个上下文中,它将对该上下文的 flexible 组列表执行轮换操作。也就是说,它将删除最后一个事件组并插入到列表的头部,从而使其有更好的被调度的机会。如前所述,这会触发来自两个上下文的所有事件的重新调度,这可以将一组不同的事件从非活动状态(即,启用但无法调度)转换为活动状态(即,启用并积极测量)。每当事件的有效状态发生变化(有效状态取决于事件的状态及其组长的状态)时,相应地跟踪事件在特定状态(活动与非活动)中花费的时间量。事件测量的缩放必须由用户空间工具执行,例如 perf stat。

一些内核版本存在与事件调度相关的错误:

  • 在内核 4.18 - rc1 之前,将软件事件添加到已经包含软件和硬件事件的现有组可能会由于事件上下文管理中的错误而失败。在创建一组事件时,这可能会导致错误。该补丁可以在这里 here 找到,它也显示了一个示例。
  • 在 4.13 - rc2 之前,由于 pinned 组是否被固定的代码中的错误,pinned 组可能不会优先与 flexible 组。这可能会导致不正确的结果。该补丁可以在这里 here 找到,它也显示了一个示例。
  • 在 4.6 -rc2 之前,多路复用事件的计时器跟踪代码中存在一个错误,其中启用的事件可能错误地显示为在 0% 的事件内被测量(即,它从未被调度算法激活)。这可能会导致不正确的结果。该补丁可以在这里 here 找到,它也显示了一个示例。

6. Event Constraints Calculation

调度算法的步骤之一是确定要调度的每个事件的约束,以便可以正确测量它。如果一个事件被分配了一个违反其约束的计数器,那么在 Intel 和 AMD 处理器上发生的情况是,事件发生时计数器永远不会递增,因此计数器值不会改变,这可能会产生没有事件发生的错误印象。正确设置约束绝对至关重要。根据以往经验,perf 是这方面最好的开源工具,但没有任何工具可以避免错误。

事件的约束在 perf 中表示为一个位向量,每个 FP 和 GP 计数器一个位,以及附加的特殊约束。Intel 和 AMD 处理器上的大多数事件只需要一个计数器,并且可以在任何 GP 计数器上进行计数。然而,一些重要的事件有更复杂的限制。例如,一个核外响应事件需要一个 GP 计数器和一个 MSR 寄存器。L1D_PEND_MISS.PENDING 事件只能在 SnB、IvB、HSW 和 BDW 上的第三个 GP 计数器上计数,但它可以在以后的微架构上的任何 GP 计数器上计数。所有上述约束都是静态已知的,因为它们不依赖任何动态因素,因此必须始终满足它们。

还有动态约束,从 Sandy Bridge 开始,如果在 BIOS 中禁用了超线程,或者如果处理器不支持超线程,则每个内核都有 8 个 GP 计数器,而不是只有 4 个。因此,是否可以在其他 4 个计数器上安排事件取决于是否超线程启用。这也取决于事件。例如,在第 9 代之前的 Sandy Bridge 上,事件代码为 0xD1(和任何单元掩码)的所有事件只能在前 4 个 GP 计数器上计数,而与超线程无关。

一些内核版本存在与事件约束相关的错误:

  • 在 4.7 -rc7 之前,当 HT 针对无法再这些计数器上计数的事件禁止时,perf 可能会使用较高的 4 GP 计数器。这些事件包括所有事件代码为 0xD0、0xD1、0xD2、0xCD 的 Broadwell 至第 9 代,以及 0xC6 的第 6 至第 9 代,在非 PEBS 模式下。这可能会导致不正确的测量。补丁可以在这里找到 here 。

  • 在 4.3 - rc4 之前,在 Broadwell 上,perf 不必要地将所有事件 0xA3(任何单位掩码)限制为仅第三个 GP 计数器,但这不仅对于具有 umask 0x08 的事件时必须的。这可能会导致调度失败。补丁可以在这里找到 here。

  • 在 4.1 -rc1 之前,perf 中有一个错误,它为具有动态约束的事件添加了不必要的动态约束。这可能会导致调度失败。补丁可以在这里找到 here。

  • 在 4.0 - rc7 之前,在 Haswell 上,perf 不必要地将所有事件 0xA3(任何单位掩码)限制为仅第三个 GP计数器,但这仅对具有 umask 0x08、0x0C 和 0x04 的事件时必需的。这可能会导致调度失败。补丁可以在这里找到 here。

  • 在 3.15 - rc7 之前,在 Silvermont 上,perf 错误地将事件 0x3C 与 umask 0x01 约束到第三个固定计数器。这可能导致不正确的测量或计划失败。补丁可以在这里找到 here。

除了 perf 中存在的错误之外,许多处理器中也存在错误,其中我们将讨论最近的 英特尔处理器中存在的两个特别讨厌的错误以及它们如何影响事件调度:TFA 错误和 HT 错误。

英特尔最近发布了一个微码更新,以修复其部分处理器受限事务内存(RTM)实施中的一个错误。作为该更新的结果,有一个名为「Performance Monitoring General Purpose Counter 3 May Contain Unexpected Values」的新勘误表,可在一下处理器的规范更新文档中找到:E3 v5、E3 v6、Xeon D、Xeon Scalable 处理器,以及第 6、第 7 和第 8 个核心。此勘误表表明,当 TSX_FORCE_ABORT MSR 的第 0 位设置为 0 时,第四个 GP 计数器(IA32_PMC3)及其控制寄存器(IA32_PREFEVTSEL3)可能会在事务执行期间损坏。这称为 TFA 错误,它仅在以下情况下存在该微码更新已安装。因此,如果 MSR 的第 0 位设置为 0,则不应使用 IA32_PMC3 和 IA32_PREFEVTSEL3,因为它们可能会损坏。但是,如果 MSR 的位 0 为 1,则所有事务都将被强制中止,并且 IA32_PMC3 将报告正确的事件计数。从内核 5.1 - rc1 开始,可以使用 /sys/devices/cpu/allow_tsx_force_abort 配置 TSX_FORCE_ABORT[0],如果设置为 0 ,则 perf 调度程序不会将 PMC3 用于任何事件。可以在此处 here 找到有关它的更多信息。如前所述,更改此设置会触发事件调度,因此效果会立即发生。幸运的是,受影响的处理器上没有事件只能在 IA32_PMC3 上计算。否则,如果 TSX_FORCE_ABORT[0] 为 0,则无法测量此类事件。

在所有英特尔 Sandy Bridge 、Ivy Bridge 和 Hashwell 处理器的规范更新文档中,另一个更令人讨厌的 bug 标题为「性能监视器计数器可能产生不正确的结果」。实际上,我在另一篇博文中非常简要地提到了这个错误。这个错误显然是有 Gooda 的作者发现的,正如题为「分层周期会计:应用程序性能调整的新方法」的论文中所讨论的那样。在此错误中,发生在一个逻辑核心中且启用了计数器的事件可能会在同级逻辑核心上的相应计数器(如果启用)上被丢弃或计数。易受此错误影响的事件称为损坏事件,包括 0xD0、0xD1、0xD2和0xD3(任何 umask)。例如,如果逻辑内核的第二个计数器被编程并启用以计数损坏事件,并且如果同级逻辑内核中的第二个计数器也被编程并启用以计数任何事件(不一定是损坏),则两个计数器中的值可能是错的。具体来说,有两种情况:

  • 如果同级内核上的事件是非破坏事件,那么它的计数器可能比实际大得多,并且破坏事件的计数可能比实际小得多。
  • 如果同级内核上的事件是破坏事件,则可能存在双向事件泄漏。

不同的内核版本以不同的方式处理这个错误。3.10 - rc1 之前的内核中的调度程序不知道这个错误,因为可能会发生损坏。在 3.10 - rc1 和 4.1-rc1 中,所有 Ivy Bridge 处理器上的所有破坏事件都被列入黑名单,因此调度程序总是无法调度这些事情。这是一个重要的限制,因为破坏事件非常有用。已发表的有关该错误的论文中提出了解决方案,并在 4.1 - rc1 中实施。基本上,调度程序已更改,以便考虑在同级逻辑核心上测量哪些事件。如果在计数器上测量破坏事件,调度程序将认为相应的计数器不可用,并且不会再其上调度任何事件。如果在计数器上测量非破坏事件。这实质上使所有破坏事件的约束都是动态的。为了确保逻辑核心不会因为同级核心调度过多的破坏事件而导致计数器不足,对逻辑核心可以使用的最大计数器数量施加了限制。在 4.1-rc1 到 4.1-rc7 中,限制是逻辑核心上启用的 FP 和 GP 计数器的总数不能超过 GP 计数器的一半,并且在启用超线程时强制执行。从 4.1-rc7 开始,限制已更改,因为启用的 GP 计数器的总数不能超过 GP 计数器的一半。此外,仅当启用超超线程并且该逻辑核心上至少有一个启用的损坏事件时,才对逻辑核心施加限制。确定此限制构成调度算法的第 3 步。请注意,这里的「启用」是指在调度时。动态禁用事件本身不会影响调度时施加的限制,因为这不会触发事件调度。但这没关系,因为调度将在下一次发生多路复用中断时执行。如果没有多路复用,那么这并不重要。

只要一种情况,调度算法可能会回溯到事件组中的较早事件,并在某个事件遇到死胡同时为其分配不同的计数器:它只回溯到重叠事件。重叠事件是一个事件,其约束权重等于或小于另一个事件的约束权重,但它的约束不是另一个事件的约束的子集。例如,假设事件 A、B、C 和 D 分别具有约束向量(二进制)1001、0011、0111 和 0111。这意味着事件 A 可以安排在第一个和第四个计数器上,事件 B 可以安排在前两个计数器中的任何一个,事件 C 和 D 可以安排在前三个计数器中的任何一个上。它们的权重分别是 2、2、3 和 3。根据调度算法,调度器会从约束权重小的事件开始,分配给它第一个可用的计数器,即计数器 0。随后,事件 B 得到计数器 1,事件 C 得到计数器 2。此时,没有计数器留给事件 D。如果没有回溯,调度事件将失败。但是调度程序将事件 A 和 B 处保留部分调度,因为两者是重叠的。它将首先尝试回溯到事件 B 的已保存状态,但是除了计数器 1 之外没有其他计数器可以分配给它。因此,它将进一步回溯到事件 A 的已保存状态,并为其分配计数器 3 而不是 0 。这样,调度程序将分别为事件 B、C 和 D 分配计数器 0、1 和 2,从而导致在该组的完整时间表中。重叠事件仅存在于 AMD 处理器上。

7. Examples

了解了 perf 中事件调度的工作原理,让我们确定本文开头显示的每个命令中的事件如何在不同的系统上调度。第一个命令式:

perf stat -e l1d_pend_miss.pending,cycle_activity.stalls_l1d_pending dd if=/dev/zero of=/dev/null count=1000000

在 SnB、IvB、HSW 和 BDW 上,最可能的输出如下,与超线程无关:

13,636,115 l1d_pend_miss.pending (49.93%)
2,715,693 cycle_activity.stalls_l1d_pending (50.07%)

有两个事件组,每个都包含一个属于核心 PMU 的硬件事件。这两个事件都限制在第三个 GP 计数器上,因此不能同时安排它们。以下是它的表现:

  • 包含 l1d_pend_miss.pending 的第一个事件组将首先安排。第一个满足事件约束的未使用计数器是第三个 GP 计数器。调度程序将该计数器分配给事件并将计数器标记为已占用。然后启用计数器,使事件处于活动状态。
  • 然后将安排仅包含 cycle_activity.stalls_l1d_pending 的第二个事件组。由于第三个 GP 计数器正在使用中,因此没有可分配给事件的可用计数器。此时调度程序失败,这导致启用多路复用。
  • 没有更多要调度的事件组,因此调度过程结束。

当发生多路复用中断时,将执行以下步骤:

  • 中断处理程序首先检查当前任务的事件上下文和/或与到期的多路复用定时器相关联的 PMU 的事件上下文是否启用了多路复用。在这种情况下,在当前运行任务的事件上下文中启用多路复用。
  • 事件上下文的 flexible 组列表的最后一个事件组从最后一个中删除并插入到列表的开头。也就是说,包含 cycle_activity.stalls_l1d_pending 的组现在位于包含 l1d_pend_miss.pending 的组之前。
  • 需要为这些事件构建一个新的计划。这与我上面所说的类似,只是 cycle_activity.stalls_l1d_pending 占用了第三个 GP计数器,而这次 l1d_pend_miss.pending 未能被调度。因此继续启用多路复用。

这两个事件在每个定时器间隔交替测量,直到程序终止。所以每个事件大约有 50% 的事件被测量。

一个人可能会得到哪些其他可能的输出? 如果 NMI Watch Dog 不知何故最终使用了第三个 GP 计数器,因为它是一个固定事件,它总是优先于我们的事件。 输出将是:

<not counted> l1d_pend_miss.pending (0.00%)
<not counted> cycle_activity.stalls_l1d_pending (0.00%)

这意味着事件在 0% 的时间内被测量。

另一个可能的输出是启用超线程并使用内核版本 4.1-rc1 或更高版本时。 同级逻辑核心的核心 PMU 在同级第三个 GP 计数器上可能发生活动破坏事件。 这使得我们逻辑核心的第三个 GP 计数器不可用,并且输出也将显示两个事件的 <not counted>。 如果破坏事件的状态动态变化,则两个事件都有机会被测量,但时间百分比加起来可能不会达到 100%,并且一个事件的百分比可能与另一个非常不同。

在 Skylake 和更高版本的处理器(事件名称略有不同)上,不会发生多路复用的情况,假设没有其他工具调用 perf_event_event 在同一逻辑核心上添加事件。 这是因为这些事件可以安排在前四个 GP 计数器中的任何一个上,并且这些处理器不会受到 HT 错误的影响。 即使存在 TFA 错误并且 NMI 中断可能占用一个 GP 计数器,这两个事件仍然会有两个 GP 计数器。

在下一个命令中,事件集是相同的。唯一不同的是列表中的第二个事件被标记为固定。

perf stat -e l1d_pend_miss.pending,cycle_activity.stalls_l1d_pending:D dd if=/dev/zero of=/dev/null count=1000000

这会导致调度程序始终将第二个事件优先于第一个事件。 Skylake 之前的微架构最可能的输出是:

<not counted> l1d_pend_miss.pending (0.00%)
1,283,966 cycle_activity.stalls_l1d_pending:D

尽管两个事件组都在相同的事件上下文中,但第一个事件组保存在 flexible 组列表中,第二个事件组保存在pinned 组列表中。 flexible 的组列表只有一个事件,因此每个多路复用中断中的轮换并没有真正的帮助。

这对 Skylake 及更高版本没有影响。

The next command is the following:

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

与前一组事件相比,有两个不同之处。 首先,l1d_pend_miss.pending 现在与另一个事件faults 位于一个组中,faults 是一个代表页面错误数量的软件事件。 这两个事件都在同一个事件组中。 其次,有一个包含 mem_uops_retired.all_loads 事件的新事件组。 Skylake 之前的一种可能输出可能是:

<not counted> l1d_pend_miss.pending (0.00%)
<not counted> faults (0.00%)
1,412,367 cycle_activity.stalls_l1d_pending:D
652,815,004 mem_uops_retired.all_loads (49.91%)

始终可以安排软件事件。 但是,由于 cycle_activity.stalls_l1d_pending 始终优先于 l1d_pend_miss.pending,因此 l1d_pend_miss.pending 事件将始终无法调度,因此其事件组作为一个整体将始终无法调度。 这解释了这两个事件的 <not counted> 部分。 cycle_activity.stalls_l1d_pending 的测量率为 100%,这应该是显而易见的原因。 这里有趣的部分是 mem_uops_retired.all_loads 只测量了 50% 的时间。 怎么了? 让我们仔细看看算法:

  • 首先安排所有 pinned 的事件组。 只有一个这样的组,即包含 cycle_activity.stalls_l1d_pending 的组。 如果启用了 NMI Watch Dog,就会有另一个组。 两者都可以成功调度。
  • flexible 列表包含两个组。 第一组将无法调度,因为没有未使用的计数器可以满足该组中硬件事件的计数。 结果启用了多路复用。
  • 第二组被检查。 由于不是纯软件事件组,而且前一组已经调度失败,所以第二组不考虑调度。 此时,没有更多要安排的组。

当多路复用中断发生时,它会循环灵活组列表并再次进行调度:

  • 所有 pinned 的事件组都像以前一样首先安排。
  • 第一个 flexible 组将被安排。 这是包含 mem_uops_retired.all_loads 的那个,可以在前四个 GP 计数器中的任何一个上调度。 由于最多占用两个 GP 计数器,因此可以成功安排事件。
  • 包含 l1d_pend_miss.pending 的第二个 flexible 组将无法如前所述进行调度。 这会导致再次启用多路复用。

这就是为什么 mem_uops_retired.all_loads 有 50% 的时间被测量。

一般来说,在 Skylake 之前安排这些活动会涉及一些额外的复杂性。 在 3.10-rc1 到(但不包括)4.1-rc1 中,在 Ivy Bridge 上,mem_uops_retired.all_loads 事件被列入黑名单,因此在使用 perf_event_open 创建它时,该事件将无法通过验证步骤。 在这种情况下,会在它旁边看到\ <not supported>。 在 4.1-rc1 到(但不包括)4.1-rc7 中,事件是允许的,但在启用超线程时逻辑核心可以使用的计数器总数(GP + FP)有限制,这是一半的 GP 计数器总数。 如果启用了 NMI Watch Dog,则无法调度 l1d_pend_miss.pending,因为一半的 GP 计数器为 2,并且该事件需要第三个计数器。 在这种情况下,会在输出中看到它旁边的 <not counted>。 在 4.1-rc7 及更高版本中,HT 错误也可能会影响调度,具体取决于同级逻辑内核中发生的情况。

在 Skylake 及更高版本上,无论 NMI Watch Dog和超线程如何,都可能没有多路复用。 不过,可能会出现一个微妙的问题。 所有三个硬件事件都只能在底部的 4 个 GP 计数器上测量。 在 4.7-rc7 之前,调度程序不知道 mem_uops_retired.all_loads 事件的这个约束,所以它认为如果 8 个 GP 计数器都可用,它可以在 8 个 GP 计数器中的任何一个上调度。 如果有更多事件组,mem_uops_retired.all_loads 可能最终被安排在前 4 个 GP 计数器之一上,并且其事件计数将显示为零。

下一个命令如下:

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

在 Skylake 之前,一种可能的输出可能是:

648,161,977 mem_load_uops_retired.l1_hit (66.61%)
4,223,144 mem_load_uops_retired.l1_miss (66.73%)
212,313 mem_load_uops_retired.l2_hit (66.66%)

这发生在 4.1-rc7 及更高版本中启用超线程时。 所有这些事件都是破坏事件,因此可以使用的最大 GP 计数器数量为 2。在任何多路复用间隔中,可以测量 3 个事件中的 2 个。 由于轮换,每个事件最终将有大约 66% 的时间被测量。 如果禁用超线程,则不会有多路复用,因为 HT 错误将不适用。 如前所述,可能会出现某些并发症。 在 Skylake 及更高版本上,最可能的输出是没有多路复用。

在算法的一个步骤中,一组中的事件根据它们的约束权重从最小到最大权重进行排序。为了说明这很有用,请考虑以下事件组:

perf stat -e '{mem_load_uops_retired.l1_hit,mem_load_uops_retired.l1_miss,mem_load_uops_retired.l2_hit ,l1d_pend_miss.pending}' dd if=/dev/zero of=/dev/null count=1000000

考虑禁用超线程的 Haswell 处理器。 前三个事件只能在底部的 4 个 GP 计数器上测量,第四个事件只能在第三个 GP 计数器上测量。 如果事件没有按照约束权重排序,前三个事件将被分配前三个 GP 计数器,这使得第四个事件无法调度,整个组将无法调度。 第四个事件的权重为 1,所有其他事件的权重为 4。根据事件的权重对事件进行排序可以成功找到该组的时间表。

下一个命令如下:

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

Skylake 之前的一种可能输出如下:

647,510,926 mem_load_uops_retired.l1_hit (40.01%)
4,100,685 mem_load_uops_retired.l1_miss (40.01%)
54,919 mem_load_uops_retired.hit_lfb (40.00%)
76,342 mem_load_uops_retired.l2_hit (39.99%)
3,343,613 mem_load_uops_retired.l3_hit (40.00%)

启用超线程后,在任何时间点,5 个事件中只有 2 个处于活动状态,即每个事件的 40% 时间。在 Skylake 及更高版本上,最可能的输出如下:

53,57,27,415 mem_load_retired.l1_hit (79.81%)
17,383 mem_load_retired.l1_miss (79.90%)
19,150 mem_load_retired.fb_hit (80.21%)
18,108 mem_load_retired.l2_hit (80.21%)
659 mem_load_retired.l3_hit (79.87%)

所有事件都限制在底部的 4 个计数器中。 因此无论是否启用超线程,5 个事件中只有 4 个可以同时处于活动状态,这相当于 80% 的时间。 但是,如果使用禁用超线程的 4.7-rc7 之前的内核版本,则不会有多路复用,因为 perf 认为这些事件中的每一个都可以在 8 个 GP 计数器中的任何一个上调度。 因此,最后一个事件的计数将为零(错误)并且不会发生多路复用。 此外,从内核 5.1-rc1 开始,TFA 错误会导致底部 4 个计数器之一不可用,从而使每个事件仅在 60% 的时间内处于活动状态。

第六个例子如下:

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

这是一个示例,其中事件在 Sunny Cove 上比在早期微架构上受到更多限制。 在 Sunny Cove,这些事件中的每一个都只能在底部的 4 个 GP 计数器上测量。 因此,由于总共有 6 个事件,因此无论是否启用超线程,都会进行多路复用。 相比之下,在早期的微架构中,每个事件都可以在 8 个 GP 计数器中的任何一个上测量,因此在禁用超线程时不会发生多路复用。

第七个例子如下:

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

这些事件与前面的示例相同,只是它们都在同一个组中,而不是将每个事件都放在自己的组中。在所有微架构上,如果禁用超线程,最可能的输出如下:

<not counted> dtlb_load_misses.walk_completed
<not counted> dtlb_load_misses.walk_completed_4k
<not counted> dtlb_store_misses.walk_completed
<not counted> dtlb_store_misses.walk_completed_4k
<not supported> itlb_misses.walk_completed
<not supported> itlb_misses.walk_completed_4k

这里有趣的部分是前 4 个事件被标记为 <not counted>,但接下来的两个事件被标记为 <not supported>。 这意味着在尝试将最后两个事件添加到组时组验证失败,因为只有 4 个 GP 计数器可用。 请注意事件旁边没有 0.00%,这是与早期案例的重要区别。

最后一个例子如下:

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

与第六个示例的唯一两个区别是,可以在 FP 计数器上安排一个额外的事件,并且要分析的任务是睡眠而不是 dd。在发生多路复用的系统上,最可能的输出是:

863,911 instructions
598 dtlb_load_misses.walk_completed
481 dtlb_load_misses.walk_completed_4k
92 dtlb_store_misses.walk_completed
74 dtlb_store_misses.walk_completed_4k
<not counted> itlb_misses.walk_completed (0.00%)
<not counted> itlb_misses.walk_completed_4k (0.00%)

以下是它的表现:

  • 前五个事件安排在一个 FP 计数器上,而 4 个可用的 GP 计数器一个接一个地安排,因为每个事件都在其自己的 flexible 组中。
  • 第六个事件会调度失败,所以启用多路复用,第七个事件不考虑调度。
  • 睡眠任务开始执行,并在第一次多路复用中断发生之前迅速将任务置于睡眠状态 1 秒。
  • 当第一次复用中断时,会看到复用被使用。 但是没有 flexible 的组列表可以轮换。 此时,睡眠任务没有运行,因此不会考虑调度其事件上下文。 结果,多路复用被禁用。
  • 稍后睡眠任务被唤醒并再次执行调度。 由于任务的 fleible 列表中的事件组没有被轮换,相同的事件会被调度,相同的事件会调度失败,从而导致复用。
  • 睡眠任务在第二轮多路复用发生之前迅速终止。 最后两个事件最终从未被测量过。 这就是为什么在这些事件旁边有<not counted> 和 0.00%。

我可以举出更复杂的例子,但我认为这对于本文来说已经足够了。

8. 碎碎念

终于把好奇的 perf 调度原理从英文啃了一遍,你说有全部了解吗?

那当然是没有的了,哪能要求全部一下子都懂呢?

走的慢一点,走的稳一点。

  • 这个世界上根本没有正确的选择,我们只不过是要努力奋斗,使当初的选择变得正确。
  • 要有意识培养自己的定力,坚持40分钟到1小时,学习就学习,吃饭就吃饭。
  • 做一个温柔的人,永远不卑不亢,清澈善良。

9. 参考资料

  • The Linux perf Event Scheduling Algorithm

  • Sandy Bridge 微架构

  • Broadwell 微架构

  • Haswell 微架构

  • Intel新微架构Sunny Cove解析:性能最高提升75%

Linux perf 事件调度算法相关推荐

  1. Python Inotify 监视LINUX文件系统事件

    Inotify 可以监视的LINUX文件系统事件包括: --IN_ACCESS,即文件被访问  --IN_MODIFY,文件被write  --IN_ATTRIB,文件属性被修改,如chmod.cho ...

  2. Java中使用JNA实现全局监听Linux键盘事件

    title: Java中使用JNA实现全局监听Linux键盘事件 date: 2019-05-03 19:08:00 Java中使用JNA实现全局监听Linux键盘事件 用JNA实现的键盘监听,在Wi ...

  3. linux下进程调度算法实验,Linux下进程调度算法的模拟实现.doc

    Linux下进程调度算法的模拟实现 枣 庄 学 院 信息科学与工程学院课程设计任务书 题目: Linux下进程调度算法的模拟实现 学 生1: 学 生2: 学 生3: 专 业: 计算机应用技术 课 程: ...

  4. java火焰_使用linux perf工具生成java程序火焰图

    Java FlameGraph(火焰图)能够非常直观的展示java程序的性能分析结果,方便发现程序热点和进一步调优.本文将展示如何使用linux perf工具生成java程序的火焰图.火焰图大致长这个 ...

  5. 电子书:《Linux Perf Master》

    电子书:<Linux Perf Master> <The Linux Perf Master>(暂用名) 是一本关于开源软件的电子书.本书与常见的专题类书籍不同,作者以应用性能 ...

  6. Linux perf获得性能计数器

    上一次讲了DVFS,但是论文中都是根据PMC计算功耗.仿真中PMC容易获得,但是实际的系统中,我们很难获得,得从linux内核源码层次访问寄存器. 简单的我们可以使用perf_event_open() ...

  7. Linux perf sched Summary

    1. Overview perf sched 使用了转储后再分析 (dump-and-post-process) 的方式来分析内核调度器的各种事件. 而这往往带来一些问题,因为这些调度事件通常非常地频 ...

  8. linux perf 参数,Linux perf命令详解及常用参数解析

    perf是Linux下的一款性能分析工具,能够进行函数级与指令级的热点查找. Perf List 利用perf剖析程序性能时,需要指定当前测试的性能时间.性能事件是指在处理器或操作系统中发生的,可能影 ...

  9. linux perf arm,linux kernel perf event(counter)

    最近接到一个客户bug,说是运行perf fuzzer的时候,手机会crash掉.当时我懵了.Perf fuzzer是什么鬼. 经过坚持不懈的google之后,终于找到了一些资料. perf coun ...

  10. 英特尔cpu支持Linux,英特尔公布对Icelake CPU的Linux Perf支持

    由于英特尔Icelake CPU的核心功能已经到位,英特尔的开源开发人员一直致力于为这些下一代处理器提供硬件支持的其他领域. 我们看到的最新的Icelake Linux补丁被Intel公开,是关于&q ...

最新文章

  1. python3 opencv_Python3 OpenCV3 图像处理基础
  2. 干货|全面理解无监督学习基础知识
  3. JUnit单元测试中的setUpBeforeClass()、tearDownAfterClass()、setUp()、tearDown()方法小结
  4. iOS linker command failed with exit code 1 (use v to see invocation)
  5. 2017美国专利榜:IBM称霸全球!华为、京东方榜上有名!
  6. 如何基于Restful ABAP Programming模型开发并部署一个支持增删改查的Fiori应用
  7. 状压动规_(POJ2817)
  8. java 垃圾回收 null_java方法中把对象置null,到底能不能加速垃圾回收
  9. 企业实战_20_MyCat使用HAPpoxy对Mycat负载均衡
  10. Spring,SpringMVC,SpringBoot,SpringCloud有什么区别和联系?
  11. 聊聊技术人的中年危机
  12. 有一种VR电影比爱情动作片更“爽”
  13. Smart3D认识引擎(Engine) Smart3D更改工程或模型(Engine)执行路径、更改模型执行优先级(建立tile的顺序)
  14. java代码实现雷达图_雷达图的一种实现! Cocos Creator !
  15. STM32入门:STM32F401CDU6库函数工程文件搭建
  16. 如何查找一篇论文的源代码
  17. html怎么做qq空间主页,如何设计qq空间
  18. Generative Adversarial Nets[AAE]
  19. Photoshop设计精讲精练(读书笔记)
  20. UIAutomatorViewer排查问题

热门文章

  1. 一文看懂什么是文本挖掘
  2. word文档中如何将软回车替换为硬回车
  3. 【mcuclub】红外测温-MLX90614
  4. Elasticsearch创建索引
  5. 基于Java的旅游门票管理系统JAVA MYSQL
  6. 期望值最大化算法 EM_GMM 估计高斯混合模型
  7. 电商经验!补单防止骗子退款技巧
  8. 江苏考生小高考计算机网,江苏小高考
  9. AWS新用户入门学习必备知识
  10. linux最高权限密码,Linux运维知识之15、login.defs(默认)登录和Linux默认权限设置(密码复杂度,相关安全设置)...