问题:在新的 linux 内核中上下文切换要慢得多

我们正在寻求将我们服务器上的操作系统从 Ubuntu 10.04 LTS 升级到 Ubuntu 12.04 LTS。不幸的是,从 2.6 内核到 3.2 内核,运行已变为可运行的线程的延迟似乎显着增加。事实上,我们得到的延迟数字令人难以置信。

让我更具体地了解测试。我们有一个运行两个线程的程序。第一个线程获取当前时间(使用 RDTSC 以刻度为单位),然后每秒发出一次条件变量信号。第二个线程等待条件变量并在收到信号时唤醒。然后它获取当前时间(使用 RDTSC 以刻度为单位)。第二个线程中的时间与第一个线程中的时间之间的差被计算并显示在控制台上。在此之后,第二个线程再次等待条件变量。大约第二次通过后,第一个线程将再次发出信号。

因此,简而言之,我们通过条件变量_延迟测量获得了一个_thread to thread通信,结果是每秒一次。

在内核 2.6.32 中,此延迟大约为 2.8-3.5 us,这是合理的。在内核 3.2.0 中,此延迟已增加到大约 40-100 us。我已经排除了两台主机之间硬件的任何差异。它们在相同的硬件上运行(双插槽 X5687 {Westmere-EP} 处理器以 3.6 GHz 运行,超线程、speedstep 和所有 C 状态均已关闭)。测试应用程序更改线程的亲和性以在同一套接字的独立物理内核上运行它们(即,第一个线程在 Core 0 上运行,第二个线程在 Core 1 上运行),因此没有线程反弹核心或套接字之间的弹跳/通信。

两台主机之间的唯一区别是一台运行内核 2.6.32-28(快速上下文切换框)的 Ubuntu 10.04 LTS,另一台运行内核 3.2.0-23(慢速上下文)的最新 Ubuntu 12.04 LTS开关盒)。所有 BIOS 设置和硬件都是相同的。

内核中是否有任何更改可以解释线程被调度运行所需的时间这种荒谬的减慢?

更新: 如果您想在您的主机和 linux 版本上运行测试,我已将代码发布到 pastebin供您阅读。编译:

g++ -O3 -o test_latency test_latency.cpp -lpthread

运行(假设您至少有一个双核盒子):

./test_latency 0 1 # Thread 1 on Core 0 and Thread 2 on Core 1

更新 2:在对内核参数、内核更改帖子和个人研究进行了大量搜索之后,我已经弄清楚了问题所在,并将解决方案发布为该问题的答案。

解答

最近内核中_bad thread wake up performance problem_的解决方案与从旧内核中使用的驱动程序acpi_idle切换到intel_idlecpuidle驱动程序有关。遗憾的是,intel_idle驱动程序忽略了用户对 C 状态的 BIOS 配置,并_根据自己的曲调_跳舞。换句话说,即使您完全禁用 PC(或服务器)BIOS 中的所有 C 状态,此驱动程序仍会在短暂不活动期间强制它们开启,这几乎总是会发生,除非一个消耗所有核心的综合基准测试(例如,压力) 在跑。您可以在大多数兼容硬件上使用出色的 Googlei7z 工具监控 C 状态转换以及与处理器频率相关的其他有用信息。

要查看当前在您的设置中哪个 cpuidle 驱动程序处于活动状态,只需将current_driver文件放入/sys/devices/system/cpucpuidle部分,如下所示:

cat /sys/devices/system/cpu/cpuidle/current_driver

如果您希望现代 Linux 操作系统具有尽可能低的上下文切换延迟,请添加以下内核启动参数以禁用所有这些省电功能:

在 Ubuntu 12.04 上,您可以通过将它们添加到/etc/default/grub中的GRUB_CMDLINE_LINUX_DEFAULT条目然后运行update-grub来执行此操作。要添加的引导参数是:

intel_idle.max_cstate=0 processor.max_cstate=0 idle=poll

以下是有关三个引导选项的作用的详细信息:

intel_idle.max_cstate设置为零会将您的 cpuidle 驱动程序恢复为acpi_idle(至少根据该选项的文档),或者完全禁用它。在我的盒子上它被完全禁用(即,在/sys/devices/system/cpu/cpuidle中显示current_driver文件会产生none的输出)。在这种情况下,不需要第二个引导选项processor.max_cstate=0。但是,文档指出将intel_idle驱动程序的 max_cstate 设置为零应该将操作系统恢复为acpi_idle驱动程序。因此,我输入了第二个引导选项以防万一。

processor.max_cstate选项将acpi_idle驱动程序的最大 C 状态设置为零,希望也禁用它。我没有可以测试它的系统,因为intel_idle.max_cstate=0完全淘汰了我可用的所有硬件上的 cpuidle 驱动程序。但是,如果您的安装确实仅使用第一个引导选项将您从intel_idle还原到acpi_idle,请让我知道第二个选项processor.max_cstate是否按照评论中记录的方式进行操作,以便我可以更新此答案。

最后,三个参数中的最后一个,idle=poll是一个真正的权力猪。它将禁用 C1/C1E,这将以更多的功耗为代价消除最后剩余的延迟,因此仅在真正需要时才使用它。对于大多数人来说,这将是多余的,因为 C1* 延迟并不是那么大。使用我在原始问题中描述的硬件上运行的测试应用程序,延迟从 9 us 变为 3 us。这对于高度延迟敏感的应用程序(例如,金融交易、高精度遥测/跟踪、高频数据采集等)来说无疑是一个显着的减少,但对于绝大多数人来说可能不值得遭受电力损失。桌面应用程序。唯一确定的方法是分析应用程序的性能改进与硬件功耗/热量的实际增加,并权衡取舍。

更新:

在对各种idle=*参数进行额外测试后,我发现如果您的硬件支持,将idle设置为mwait是一个更好的主意。似乎使用MWAIT/MONITOR指令允许 CPU 进入 C1E,而不会将任何明显的延迟添加到线程唤醒时间。使用idle=mwait,您将获得更低的 CPU 温度(与idle=poll相比)、更少的功耗,并且仍然保持轮询空闲循环的出色低延迟。因此,基于这些发现,我为低 CPU 线程唤醒延迟而更新的推荐引导参数集是:

intel_idle.max_cstate=0 processor.max_cstate=0 idle=mwait

使用idle=mwait而不是idle=poll也可能有助于启动 Turbo Boost(通过帮助 CPU 保持在其 TDP [Thermal Design Power] 以下)和超线程(MWAIT 是不消耗整个物理内核的理想机制)同时避免更高的C状态)。然而,这尚未在测试中得到证明,我将继续这样做。

更新 2:

mwaitidle 选项已从较新的 3.x 内核](https://lkml.org/lkml/2013/2/10/21)中删除[(感谢用户 ck_ 的更新)。这给我们留下了两个选择:

idle=halt- 应该和mwait一样好用,但要测试以确保您的硬件就是这种情况。HLT指令几乎等同于状态提示为0的MWAIT。问题在于退出HLT状态需要中断,而可以使用内存写入(或中断)退出MWAIT状态。根据 Linux 内核在其空闲循环中使用的内容,这可能会使 MWAIT 更有效率。所以,正如我所说的测试/配置文件,看看它是否满足您的延迟需求......

idle=poll- 性能最高的选项,但以功耗和热量为代价。

Logo

更多推荐