NO_HZ:减少调度时钟节拍

本文档描述了可减少调度时钟中断次数的 Kconfig 选项和引导参数,从而提高能效并减少操作系统抖动。减少操作系统抖动对于某些类型的计算密集型高性能计算 (HPC) 应用程序和实时应用程序非常重要。

管理调度时钟中断(也称为“调度时钟节拍”或简称“节拍”)主要有三种方法:

  1. 永不忽略调度时钟节拍 (CONFIG_HZ_PERIODIC=y 或 CONFIG_NO_HZ=n,适用于较旧的内核)。您通常不希望选择此选项。

  2. 在空闲 CPU 上忽略调度时钟节拍 (CONFIG_NO_HZ_IDLE=y 或 CONFIG_NO_HZ=y,适用于较旧的内核)。这是最常见的方法,应该作为默认设置。

  3. 在空闲或只有一个可运行任务的 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 被称为“动态节拍空闲”、“处于动态节拍空闲模式”、“处于 nohz 模式”或“正在运行无节拍”。本文档的其余部分将使用“动态节拍空闲模式”。

CONFIG_NO_HZ_IDLE=y Kconfig 选项会导致内核避免向空闲 CPU 发送调度时钟中断,这对于电池供电的设备和高度虚拟化的大型机都至关重要。运行 CONFIG_HZ_PERIODIC=y 内核的电池供电设备会非常快地耗尽其电池,其速度很容易是运行 CONFIG_NO_HZ_IDLE=y 内核的相同设备的两到三倍。运行 1,500 个操作系统实例的大型机可能会发现其一半的 CPU 时间被不必要的调度时钟中断消耗。在这些情况下,强烈希望避免向空闲 CPU 发送调度时钟中断。也就是说,动态节拍空闲模式不是免费的

  1. 它增加了在进出空闲循环的路径上执行的指令数量。

  2. 在许多架构上,动态节拍空闲模式还会增加昂贵的时钟重新编程操作的数量。

因此,具有严格实时响应约束的系统通常运行 CONFIG_HZ_PERIODIC=y 内核(或对于较旧的内核,使用 CONFIG_NO_HZ=n),以避免降低从空闲转换的延迟。

还有一个引导参数“nohz=”,可以通过指定“nohz=off”来禁用 CONFIG_NO_HZ_IDLE=y 内核中的动态节拍空闲模式。默认情况下,CONFIG_NO_HZ_IDLE=y 内核以“nohz=on”引导,启用动态节拍空闲模式。

对于只有一个可运行任务的 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 回调排入队列。

就像动态节拍空闲模式一样,自适应节拍模式的好处也不是免费的

  1. CONFIG_NO_HZ_FULL 选择 CONFIG_NO_HZ_COMMON,因此您无法在不运行动态节拍空闲的情况下运行自适应节拍。这种依赖关系会延伸到实现中,因此 CONFIG_NO_HZ_IDLE 的所有成本也会由 CONFIG_NO_HZ_FULL 承担。

  2. 由于需要通知内核子系统(例如 RCU)模式的更改,用户/内核转换的成本略高。

  3. POSIX CPU 定时器会阻止 CPU 进入自适应节拍模式。需要根据 CPU 时间消耗采取行动的实时应用程序需要使用其他方式来执行此操作。

  4. 如果有比硬件可以容纳的更多的 perf 事件挂起,它们通常会轮询,以便随着时间的推移收集所有事件。自适应节拍模式可能会阻止这种轮询发生。这可能会通过阻止挂起大量 perf 事件的 CPU 进入自适应节拍模式来解决。

  5. 自适应节拍 CPU 的调度程序统计信息可能与非自适应节拍 CPU 的计算方式略有不同。这反过来可能会扰乱实时任务的负载平衡。

尽管预计会随着时间的推移而改进,但自适应节拍对于许多类型的实时和计算密集型应用程序非常有用。但是,上面列出的缺点意味着默认情况下不应启用自适应节拍。

RCU 影响

在某些情况下,不允许空闲 CPU 进入动态节拍空闲模式或自适应节拍模式,最常见的情况是当该 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 上。否则,调度器将决定在何处运行它们,这可能符合也可能不符合您的预期。

测试

因此,您启用了本文档中描述的所有 OS 抖动特性,但没有看到工作负载的行为发生任何变化。这是因为您的工作负载受 OS 抖动的影响不大,还是因为有其他因素在起作用?本节通过提供一个简单的 OS 抖动测试套件来帮助回答这个问题,该套件可在以下 git 存档的 master 分支上找到

git://git.kernel.org/pub/scm/linux/kernel/git/frederic/dynticks-testing.git

克隆此存档并按照 README 文件中的说明进行操作。此测试过程将生成一个跟踪,使您可以评估是否已成功从系统中删除了 OS 抖动。如果此跟踪表明您已尽可能地移除了 OS 抖动,那么您可以得出结论,您的工作负载对 OS 抖动并不那么敏感。

注意:此测试要求您的系统至少有两个 CPU。我们目前没有很好的方法从单 CPU 系统中消除 OS 抖动。

已知问题

  • Dyntick-idle 会稍微减慢进入和退出空闲状态的转换。实际上,这并没有造成问题,除了最激进的实时工作负载,它们可以选择禁用 dyntick-idle 模式,而大多数工作负载都采用了这种选择。但是,某些工作负载无疑会希望使用自适应节拍来消除调度时钟中断的延迟。以下是这些工作负载的一些选项

    1. 使用用户空间的 PMQOS 向内核告知您的延迟要求(首选)。

    2. 在 x86 系统上,使用 “idle=mwait” 启动参数。

    c. 在 x86 系统上,使用 “intel_idle.max_cstate=” 来限制 ` 最大 C 状态深度。

    1. 在 x86 系统上,使用 “idle=poll” 启动参数。但是,请注意,使用此参数可能会导致您的 CPU 过热,这可能会导致热节流来降低您的延迟 -- 并且这种降级甚至可能比 dyntick-idle 更糟糕。此外,此参数实际上禁用了 Intel CPU 上的睿频模式,这会显着降低最大性能。

  • 自适应节拍会稍微减慢用户/内核转换。对于计算密集型工作负载来说,这预计不会有问题,因为这些工作负载的转换很少。需要进行仔细的基准测试,以确定其他工作负载是否会受到这种影响的显著影响。

  • 自适应节拍不会执行任何操作,除非给定的 CPU 只有一个可运行的任务,即使在许多其他情况下不需要调度时钟节拍。举一个例子,考虑一个 CPU,它有一个可运行的高优先级 SCHED_FIFO 任务和任意数量的低优先级 SCHED_OTHER 任务。在这种情况下,CPU 需要运行 SCHED_FIFO 任务,直到它阻塞或一些其他更高优先级的任务在此 CPU 上唤醒(或被分配到该 CPU),因此没有必要向该 CPU 发送调度时钟中断。但是,当前的实现仍然向具有单个可运行 SCHED_FIFO 任务和多个可运行 SCHED_OTHER 任务的 CPU 发送调度时钟中断,即使这些中断是不必要的。

    即使给定的 CPU 上有多个可运行的任务,在当前运行的任务的时间片过期之前中断该 CPU 也没有意义,而这几乎总是比下一个调度时钟中断的时间长得多。

    更好地处理这些情况是未来的工作。

  • 需要重新启动才能重新配置自适应空闲和 RCU 回调卸载。如果需要,可以提供运行时重新配置,但是,由于在运行时重新配置 RCU 的复杂性,需要有非常充分的理由。特别是考虑到您有一个简单的选择,即简单地从所有 CPU 卸载 RCU 回调,并在您需要将它们固定在任何位置时将它们固定在任何位置。

  • 需要额外的配置来处理其他 OS 抖动源,包括中断和系统实用程序任务和进程。此配置通常涉及将中断和任务绑定到特定的 CPU。

  • 某些 OS 抖动源目前只能通过约束工作负载来消除。例如,消除由于全局 TLB 射击导致的 OS 抖动的唯一方法是避免导致这些射击的取消映射操作(例如内核模块卸载操作)。再举一个例子,可以通过使用巨页并通过限制应用程序使用的内存量来减少(在某些情况下消除)页面错误和 TLB 未命中。预先加载工作集也可能会有所帮助,尤其是与 mlock() 和 mlockall() 系统调用结合使用时。

  • 除非所有 CPU 都处于空闲状态,否则至少一个 CPU 必须保持调度时钟中断的运行,以支持精确的时间保持。

  • 如果可能存在一些自适应节拍 CPU,则至少会有一个 CPU 保持调度时钟中断的运行,即使所有 CPU 都处于其他空闲状态。

    更好地处理这种情况是正在进行的工作。

  • 某些进程处理操作仍然需要偶尔的调度时钟节拍。这些操作包括计算 CPU 负载、维护调度平均值、计算 CFS 实体 vruntime、计算 avenrun 和执行负载平衡。目前,它们通过每秒左右的调度时钟节拍来适应。正在进行的工作将消除甚至这些不频繁的调度时钟节拍的需要。