101

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

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

因此,简而言之,我们通过条件变量延迟测量获得一个线程到线程的通信,结果是每秒一次。

在内核 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:经过内核参数、内核更改帖子和个人研究的大量搜索之后,我已经弄清楚了问题所在,并将解决方案发布为这个问题的答案。

4

3 回答 3

96

在最近的内核中,坏线程唤醒性能问题的解决方案与从旧内核中使用的驱动程序切换到intel_idlecpuidle 驱动程序有关。acpi_idle可悲的是,intel_idle驱动程序忽略了 C 状态的用户 BIOS 配置,并随心所欲地跳舞。换句话说,即使您完全禁用 PC(或服务器)BIOS 中的所有 C 状态,此驱动程序仍会在短暂不活动期间强制它们打开,这几乎总是会发生,除非一个消耗所有核心的综合基准测试(例如,压力) 在跑。您可以在大多数兼容硬件上使用出色的 Google i7z 工具监控 C 状态转换以及与处理器频率相关的其他有用信息。

要查看您的设置中当前处于活动状态的 cpuidle 驱动程序,只需在以下部分中对current_driver文件进行分类:cpuidle/sys/devices/system/cpu

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

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

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

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

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

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

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

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

更新:

在使用各种idle=*参数进行额外测试后,我发现设置idlemwait如果您的硬件支持是一个更好的主意。似乎这些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 [热设计功率])和超线程(MWAIT 是同时不消耗整个物理内核的理想机制)时间避免较高的 C 状态)。然而,这尚未在测试中得到证明,我将继续这样做。

更新 2:

mwaitidle 选项已从较新的 3.x 内核中删除(感谢用户 ck_ 的更新)。这给我们留下了两个选择:

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

idle=poll- 最高性能的选择,以牺牲电力和热量为代价。

于 2012-08-24T22:10:54.317 回答
8

也许变慢的是 futex,它是条件变量的构建块。这将带来一些启示:

strace -r ./test_latency 0 1 &> test_latency_strace & sleep 8 && killall test_latency

然后

for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done

这将显示有趣的系统调用所花费的微秒,按时间排序。

在内核 2.6.32 上

$ for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done
futex
 1.000140 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000129 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000124 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000119 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000106 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000103 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000102 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 0.000125 futex(0x7f98ce4c0b88, FUTEX_WAKE_PRIVATE, 2147483647) = 0
 0.000042 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000038 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000037 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000030 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000029 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 0
 0.000028 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000027 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000018 futex(0x7fff82f0ec3c, FUTEX_WAKE_PRIVATE, 1) = 0
nanosleep
 0.000027 nanosleep({1, 0}, {1, 0}) = 0
 0.000019 nanosleep({1, 0}, {1, 0}) = 0
 0.000019 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, 0x7fff82f0eb40) = ? ERESTART_RESTARTBLOCK (To be restarted)
 0.000017 nanosleep({1, 0}, {1, 0}) = 0
rt_sig
 0.000045 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000040 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000038 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000034 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000033 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000032 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000032 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000028 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000028 rt_sigaction(SIGRT_1, {0x37f8c052b0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x37f8c0e4c0}, NULL, 8) = 0
 0.000027 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000027 rt_sigaction(SIGRTMIN, {0x37f8c05370, [], SA_RESTORER|SA_SIGINFO, 0x37f8c0e4c0}, NULL, 8) = 0
 0.000027 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000023 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000023 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000022 rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
 0.000022 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000021 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000021 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000019 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0

在内核 3.1.9 上

$ for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done
futex
 1.000129 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000126 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000122 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000115 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000114 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000112 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000109 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 0.000139 futex(0x3f8b8f2fb0, FUTEX_WAKE_PRIVATE, 2147483647) = 0
 0.000043 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000041 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000037 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000036 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000034 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000034 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
nanosleep
 0.000025 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000022 nanosleep({1, 0}, {0, 3925413}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
 0.000021 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
rt_sig
 0.000045 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000044 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000043 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000040 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000038 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000037 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000036 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000036 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000035 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000034 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigaction(SIGRT_1, {0x3f892067b0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x3f8920f500}, NULL, 8) = 0
 0.000026 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000026 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000024 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000023 rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
 0.000023 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000022 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000019 rt_sigaction(SIGRTMIN, {0x3f89206720, [], SA_RESTORER|SA_SIGINFO, 0x3f8920f500}, NULL, 8) = 0

我发现了这个5 年前的错误报告,其中包含一个“乒乓”性能测试,比较

  1. 单线程 libpthread 互斥锁
  2. libpthread 条件变量
  3. 普通的旧 Unix 信号

我不得不添加

#include <stdint.h>

为了编译,我用这个命令做了

g++ -O3 -o condvar-perf condvar-perf.cpp -lpthread -lrt

在内核 2.6.32 上

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    29085 us; per iteration:   29 ns / 9.4e-05 context switches.
c.v. ping-pong test   elapsed:  4771993 us; per iteration: 4771 ns / 4.03 context switches.
signal ping-pong test elapsed:  8685423 us; per iteration: 8685 ns / 4.05 context switches.

在内核 3.1.9 上

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    26811 us; per iteration:   26 ns / 8e-06 context switches.
c.v. ping-pong test   elapsed: 10930794 us; per iteration: 10930 ns / 4.01 context switches.
signal ping-pong test elapsed: 10949670 us; per iteration: 10949 ns / 4.01 context switches.

我得出的结论是,内核 2.6.32 和 3.1.9 之间的上下文切换确实变慢了,尽管没有您在内核 3.2 中观察到的那么多。我意识到这还没有回答你的问题,我会继续挖掘。

编辑:我发现更改进程的实时优先级(两个线程)可以提高 3.1.9 的性能以匹配 2.6.32。但是,在 2.6.32 上设置相同的优先级会使其速度变慢……看图 - 我会更多地研究它。

这是我现在的结果:

在内核 2.6.32 上

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    29629 us; per iteration:   29 ns / 0.000418 context switches.
c.v. ping-pong test   elapsed:  6225637 us; per iteration: 6225 ns / 4.1 context switches.
signal ping-pong test elapsed:  5602248 us; per iteration: 5602 ns / 4.09 context switches.
$ chrt -f 1 ./condvar-perf 1000000
NPTL
mutex                 elapsed:    29049 us; per iteration:   29 ns / 0.000407 context switches.
c.v. ping-pong test   elapsed: 16131360 us; per iteration: 16131 ns / 4.29 context switches.
signal ping-pong test elapsed: 11817819 us; per iteration: 11817 ns / 4.16 context switches.
$ 

在内核 3.1.9 上

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    26830 us; per iteration:   26 ns / 5.7e-05 context switches.
c.v. ping-pong test   elapsed: 12812788 us; per iteration: 12812 ns / 4.01 context switches.
signal ping-pong test elapsed: 13126865 us; per iteration: 13126 ns / 4.01 context switches.
$ chrt -f 1 ./condvar-perf 1000000
NPTL
mutex                 elapsed:    27025 us; per iteration:   27 ns / 3.7e-05 context switches.
c.v. ping-pong test   elapsed:  5099885 us; per iteration: 5099 ns / 4 context switches.
signal ping-pong test elapsed:  5508227 us; per iteration: 5508 ns / 4 context switches.
$ 
于 2012-08-24T16:43:25.817 回答
1

由于pstate驱动程序与 c-states 是分开的,您可能还会看到处理器在更新的进程和 Linux 内核中单击。因此,此外,要禁用此功能,请使用以下内核参数:

intel_pstate=disable

于 2015-08-11T15:08:37.787 回答