NO_HZ:减少调度时钟节拍¶
本文档介绍了 Kconfig 选项和启动参数,这些选项和参数可以减少调度时钟中断的数量,从而提高能源效率并减少操作系统抖动。减少操作系统抖动对于某些类型的计算密集型高性能计算 (HPC) 应用程序和实时应用程序非常重要。
有三种主要方法来管理调度时钟中断(也称为“调度时钟节拍”或简称为“节拍”)
永不省略调度时钟节拍(对于旧内核,CONFIG_HZ_PERIODIC=y 或 CONFIG_NO_HZ=n)。通常您-不-会希望选择此选项。
在空闲 CPU 上省略调度时钟节拍(对于旧内核,CONFIG_NO_HZ_IDLE=y 或 CONFIG_NO_HZ=y)。这是最常见的方法,应该是默认设置。
在空闲或只有一个可运行任务的 CPU 上省略调度时钟节拍 (CONFIG_NO_HZ_FULL=y)。除非您正在运行实时应用程序或某些类型的 HPC 工作负载,否则通常您-不-会希望使用此选项。
以下三个部分描述了这三种情况,然后是关于 RCU 特定考虑事项的第三部分,讨论测试的第四部分以及列出已知问题的第五个也是最后一个部分。
永不省略调度时钟节拍¶
来自 1990 年代和 2000 年代初期的非常旧版本的 Linux 无法省略调度时钟节拍。事实证明,在某些情况下,这种老式方法仍然是正确的方法,例如,在使用短 CPU 突发的许多任务的繁重工作负载中,存在非常频繁的空闲期,但这些空闲期也很短(数十或数百微秒)。对于这些类型的工作负载,通常会传递调度时钟中断,因为每个 CPU 通常会有多个可运行任务。在这些情况下,尝试关闭调度时钟中断除了增加在空闲状态之间切换以及在用户和内核执行之间转换的开销之外,不会产生任何影响。
可以使用 CONFIG_HZ_PERIODIC=y(或者对于旧内核,CONFIG_NO_HZ=n)选择此操作模式。
但是,如果您正在运行具有较长空闲期的轻型工作负载,则无法省略调度时钟中断将导致过多的功耗。这在电池供电设备上尤其糟糕,因为它会导致电池寿命极短。如果您正在运行轻型工作负载,那么您应该阅读以下部分。
此外,如果您正在运行实时工作负载或具有短迭代的 HPC 工作负载,则调度时钟中断可能会降低您的应用程序性能。如果这描述了您的工作负载,您应该阅读以下两个部分。
对于空闲 CPU,省略调度时钟节拍¶
如果 CPU 空闲,则向其发送调度时钟中断没有多大意义。毕竟,调度时钟中断的主要目的是迫使繁忙的 CPU 在多个任务之间转移注意力,而空闲的 CPU 没有任务可以转移注意力。
未接收调度时钟中断的空闲 CPU 被称为“dyntick-idle”、“处于 dyntick-idle 模式”、“处于 nohz 模式”或“运行无节拍”。本文档的其余部分将使用“dyntick-idle 模式”。
CONFIG_NO_HZ_IDLE=y Kconfig 选项使内核避免向空闲 CPU 发送调度时钟中断,这对于电池供电设备和高度虚拟化的大型机都至关重要。运行 CONFIG_HZ_PERIODIC=y 内核的电池供电设备会非常快地耗尽其电池,其速度很容易是运行 CONFIG_NO_HZ_IDLE=y 内核的同一设备的 2-3 倍。运行 1,500 个操作系统实例的大型机可能会发现一半的 CPU 时间被不必要的调度时钟中断消耗掉。在这些情况下,有强烈的动机避免向空闲 CPU 发送调度时钟中断。也就是说,dyntick-idle 模式不是免费的
它增加了在进入和退出空闲循环的路径上执行的指令数量。
在许多架构上,dyntick-idle 模式还会增加昂贵的时钟重新编程操作的数量。
因此,具有严格实时响应约束的系统通常运行 CONFIG_HZ_PERIODIC=y 内核(对于旧内核,CONFIG_NO_HZ=n),以避免降低从空闲状态转换的延迟。
还有一个启动参数“nohz=”,可用于通过指定“nohz=off”来禁用 CONFIG_NO_HZ_IDLE=y 内核中的 dyntick-idle 模式。默认情况下,CONFIG_NO_HZ_IDLE=y 内核以“nohz=on”启动,从而启用 dyntick-idle 模式。
对于只有一个可运行任务的 CPU,省略调度时钟节拍¶
如果 CPU 只有一个可运行任务,则向其发送调度时钟中断没有多大意义,因为没有其他任务可以切换到。请注意,对于只有一个可运行任务的 CPU 省略调度时钟节拍意味着也对空闲 CPU 省略它们。
CONFIG_NO_HZ_FULL=y Kconfig 选项使内核避免向具有单个可运行任务的 CPU 发送调度时钟中断,并且此类 CPU 被称为“自适应节拍 CPU”。这对于具有严格实时响应约束的应用程序非常重要,因为它允许它们将最坏情况下的响应时间提高调度时钟中断的最大持续时间。对于计算密集型的短迭代工作负载也很重要:如果在给定的迭代期间延迟了任何 CPU,则所有其他 CPU 将被迫等待空闲状态,而延迟的 CPU 完成。因此,延迟乘以小于 CPU 数量的 1。在这些情况下,再次有强烈的动机避免发送调度时钟中断。
默认情况下,没有 CPU 将是自适应节拍 CPU。“nohz_full=”启动参数指定自适应节拍 CPU。例如,“nohz_full=1,6-8”表示 CPU 1、6、7 和 8 是自适应节拍 CPU。请注意,您被禁止将所有 CPU 标记为自适应节拍 CPU:必须至少有一个非自适应节拍 CPU 保持在线以处理计时任务,以确保诸如 gettimeofday() 之类的系统调用在自适应节拍 CPU 上返回准确的值。(对于 CONFIG_NO_HZ_IDLE=y,这不是问题,因为没有运行的用户进程可以观察到时钟速率的轻微漂移。)请注意,这意味着您的系统必须至少有两个 CPU,CONFIG_NO_HZ_FULL=y 才能为您做任何事情。
最后,自适应节拍 CPU 必须卸载其 RCU 回调。这将在下面的“RCU 含义”部分中介绍。
通常,CPU 会尽可能长时间地保持在自适应节拍模式下。特别是,转换为内核模式不会自动更改模式。相反,CPU 仅在需要时才退出自适应节拍模式,例如,如果该 CPU 将 RCU 回调排队。
就像 dyntick-idle 模式一样,自适应节拍模式的好处不是免费的
CONFIG_NO_HZ_FULL 选择 CONFIG_NO_HZ_COMMON,因此您不能在不运行 dyntick 空闲状态的情况下运行自适应节拍。此依赖关系延伸到实现中,因此 CONFIG_NO_HZ_IDLE 的所有成本也由 CONFIG_NO_HZ_FULL 承担。
由于需要通知内核子系统(如 RCU)有关模式更改,因此用户/内核转换稍微贵一些。
POSIX CPU 定时器阻止 CPU 进入自适应节拍模式。需要根据 CPU 时间消耗采取措施的实时应用程序需要使用其他方法来执行此操作。
如果有比硬件可以容纳的更多 perf 事件挂起,它们通常会进行循环,以便随着时间的推移收集所有事件。自适应节拍模式可能会阻止此循环的发生。这可能会通过阻止具有大量挂起 perf 事件的 CPU 进入自适应节拍模式来解决。
自适应节拍 CPU 的调度器统计信息可能与非自适应节拍 CPU 的计算方式略有不同。这反过来可能会扰乱实时任务的负载平衡。
虽然预计随着时间的推移会有所改进,但自适应节拍对于许多类型的实时和计算密集型应用程序非常有用。但是,上面列出的缺点意味着不应(尚未)默认启用自适应节拍。
RCU 含义¶
在某些情况下,不允许空闲 CPU 进入 dyntick-idle 模式或自适应节拍模式,最常见的是该 CPU 具有挂起的 RCU 回调。
使用 CONFIG_RCU_NOCB_CPU=y Kconfig 选项将 RCU 回调处理卸载到“rcuo” kthread 来避免这种情况。可以使用“rcu_nocbs=”内核启动参数选择要卸载的特定 CPU,该参数采用以逗号分隔的 CPU 和 CPU 范围列表,例如,“1,3-5”选择 CPU 1、3、4 和 5。请注意,由“nohz_full”内核启动参数指定的 CPU 也被卸载。
卸载的 CPU 将永远不会将 RCU 回调排队,因此 RCU 永远不会阻止卸载的 CPU 进入 dyntick-idle 模式或自适应节拍模式。也就是说,请注意,如果需要,由用户空间将“rcuo” kthread 固定到特定 CPU。否则,调度器将决定在哪里运行它们,这可能不是您希望它们运行的位置。
测试¶
因此,您启用了本文档中描述的所有操作系统抖动功能,但没有看到您的工作负载行为发生任何变化。是因为您的工作负载受操作系统抖动的影响不大,还是因为其他因素在起作用?本节通过提供一个简单的操作系统抖动测试套件来帮助回答这个问题,该套件可在以下 git 存档的 master 分支上找到
git://git.kernel.org/pub/scm/linux/kernel/git/frederic/dynticks-testing.git
克隆此存档并按照 README 文件中的说明进行操作。此测试过程将生成一个跟踪,您可以使用该跟踪来评估是否已成功从系统中删除操作系统抖动。如果此跟踪显示您已尽可能多地删除了操作系统抖动,那么您可以得出结论,您的工作负载对操作系统抖动并不那么敏感。
注意:此测试要求您的系统至少有两个 CPU。我们目前没有很好的方法从单 CPU 系统中删除操作系统抖动。
已知问题¶
Dyntick-idle 会稍微减慢进入和退出空闲状态的转换速度。实际上,这并不是问题,除非对于最具侵略性的实时工作负载,它们可以选择禁用 dyntick-idle 模式,这是它们大多数都选择的选项。但是,毫无疑问,某些工作负载将希望使用自适应节拍来消除调度时钟中断延迟。以下是这些工作负载的一些选项
使用来自用户空间的 PMQOS 来通知内核您的延迟要求(首选)。
在 x86 系统上,使用“idle=mwait”启动参数。
c. 在 x86 系统上,使用“intel_idle.max_cstate=”来限制 ` 最大 C 状态深度。
在 x86 系统上,使用“idle=poll”启动参数。但是,请注意,使用此参数会导致您的 CPU 过热,这可能会导致热节流降低您的延迟 - 并且这种降低甚至可能比 dyntick-idle 的降低更严重。此外,此参数有效地禁用了 Intel CPU 上的 Turbo Mode,这会显着降低最大性能。
自适应节拍会稍微减慢用户/内核转换的速度。预计这对于计算密集型工作负载来说不是问题,因为它们很少进行此类转换。需要仔细的基准测试才能确定其他工作负载是否会受到此效应的显着影响。
仅当给定 CPU 只有一个可运行任务时,自适应节拍才会执行任何操作,即使在许多其他情况下不需要调度时钟节拍。举一个例子,考虑一个 CPU,它有一个可运行的高优先级 SCHED_FIFO 任务和任意数量的低优先级 SCHED_OTHER 任务。在这种情况下,要求 CPU 运行 SCHED_FIFO 任务,直到它阻塞或某个其他更高优先级的任务在此 CPU 上唤醒(或分配给此 CPU),因此没有必要向此 CPU 发送调度时钟中断。但是,当前实现仍然向具有单个可运行 SCHED_FIFO 任务和多个可运行 SCHED_OTHER 任务的 CPU 发送调度时钟中断,即使这些中断是不必要的。
即使在给定的 CPU 上有多个可运行任务,在当前运行任务的时间片到期之前中断该 CPU 也没有多大意义,这几乎总是比下一个调度时钟中断的时间长得多。
更好地处理这些情况是未来的工作。
需要重新启动才能重新配置自适应空闲和 RCU 回调卸载。如果需要,可以提供运行时重新配置,但是,由于在运行时重新配置 RCU 的复杂性,需要有一个非常充分的理由。特别是考虑到您可以通过简单地从所有 CPU 卸载 RCU 回调并将它们固定在您希望它们被固定的地方来随时随地执行此操作。
需要额外的配置来处理其他操作系统抖动源,包括中断和系统实用程序任务和进程。此配置通常涉及将中断和任务绑定到特定的 CPU。
某些操作系统抖动源目前只能通过约束工作负载来消除。例如,消除由于全局 TLB 刷新导致的操作系统抖动的唯一方法是避免导致这些刷新的取消映射操作(例如内核模块卸载操作)。对于另一个示例,可以使用大页面并通过约束应用程序使用的内存量来减少(并且在某些情况下消除)页面错误和 TLB 未命中。预先错误地处理工作集也可能很有帮助,尤其是与 mlock() 和 mlockall() 系统调用结合使用时。
除非所有 CPU 都处于空闲状态,否则至少一个 CPU 必须保持调度时钟中断运行,以支持准确的计时。
如果可能存在一些自适应节拍 CPU,则至少会有一个 CPU 保持调度时钟中断运行,即使所有 CPU 都在其他情况下处于空闲状态。
更好地处理这种情况是正在进行的工作。
某些进程处理操作仍然需要偶尔的调度时钟节拍。这些操作包括计算 CPU 负载、维护调度平均值、计算 CFS 实体 vruntime、计算 avenrun 和执行负载平衡。它们目前通过每秒左右调度时钟节拍来容纳。正在进行的工作将消除即使是这些不频繁的调度时钟节拍的需要。