在新的Linux内核中,上下文切换速度要慢得多

我们希望将我们的服务器上的操作系统从Ubuntu 10.04 LTS升级到Ubuntu 12.04 LTS。 不幸的是,运行已经可运行的线程的延迟似乎已经从2.6内核显着增加到了3.2内核。 实际上,我们正在得到的延迟数字是难以相信的。

让我更具体的testing。 我们有一个运行两个线程的程序。 第一个线程获取当前时间(以RDTSC为单位),然后每秒发送一个条件variables。 第二个线程在条件variables上等待并在发送信号时唤醒。 然后获取当前时间(以RDTSC为单位)。 计算第二个线程中的时间和第一个线程中的时间之间的差值并显示在控制台上。 之后,第二个线程再次等待条件variables。 大约一秒钟后,第一个线程会再次发出信号。

所以,简而言之,我们得到一个线程,通过条件variables等待时间测量线程进行一次线程通信

在内核2.6.32中,这个等待时间在2.8-3.5 us左右,这是合理的。 在内核3.2.0中,这个延迟已经增加到40-100 us的量级。 我排除了两台主机之间的硬件差异。 它们运行在相同的硬件上(运行于3.6 GHz的双插槽X5687 {Westmere-EP}处理器,具有超线程,speedstep和所有C状态closures)。 testing应用程序改变线程的亲和力,以便在同一个套接字的独立物理核心上运行它们(即,第一个线程在核心0上运行,第二个线程在核心1上运行),所以线程没有弹起核心或弹跳/sockets之间的通信。

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

内核中是否有任何变化可以解释为什么这个线程被安排运行需要多长时间?

更新:如果你想在你的主机和linux版本上运行testing,我已经把代码发布到了pastebin中供你阅读。 编译:

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

运行(假设你至less有一个双核心盒):

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

更新2 :经过内核参数的大量search,关于内核变化和个人研究的post,我已经找出问题所在,并将解决scheme作为这个问题的答案。

在最近的内核中, 线程唤醒性能问题的解决scheme与从旧的内核中使用的驱动程序acpi_idle切换到intel_idle cpuidle驱动程序有关。 可悲的是, intel_idle驱动程序忽略了C状态的用户BIOSconfiguration,并跳舞到自己的曲调 。 换句话说,即使您完全禁用了PC(或服务器)的BIOS中的所有C状态,这个驱动程序仍然会在短时间不活动期间强制它们运行,除非全部核心消耗综合基准(例如,压力) 在跑。 您可以在大多数兼容的硬件上使用精彩的Google i7z工具来监控C状态转换,以及与处理器频率相关的其他有用信息。

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

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

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

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

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

以下是关于三种启动选项的具体细节:

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

processor.max_cstate选项将acpi_idle驱动程序的最大C状态设置为零,并希望禁用它。 我没有一个系统,我可以testing这个,因为intel_idle.max_cstate=0完全淘汰所有可用的硬件cpuidle驱动程序。 但是,如果您的安装将intel_idle恢复为仅使用第一个引导选项的acpi_idle ,请让我知道是否第二个选项processor.max_cstate在注释中做了logging,以便我可以更新此答案。

最后,三个参数中的最后一个, idle=poll是一个真正的权力。 它将禁用C1 / C1E,这会消耗更多功耗的代价来消除最后剩余的延迟,所以只有在真正需要的时候才使用它。 对于大多数这将是矫枉过正,因为C1 *延迟并不是那么大。 使用我在原始问题中描述的硬件上运行的testing应用程序,延迟从9 us到3 us。 这对于高度延迟敏感的应用(例如,金融交易,高精度遥测/跟踪,高频数据采集等)而言肯定是显着的降低,但是可能不值得为绝大多数的桌面应用。 唯一可以确定的方法是分析应用程序在性能方面的改进与硬件的功耗/散热的实际增长,并权衡权衡。

更新:

经过各种idle=*参数的额外testing后,我发现如果你的硬件支持,设置idle mwait是一个好主意。 看起来,使用MWAIT/MONITOR指令允许CPUinputC1E,而不会在线程唤醒时间中添加任何明显的延迟。 随着idle=mwait ,你会得到更酷的CPU温度(相比, idle=poll ),更less的电力使用,并仍然保持轮询空闲循环出色的低延迟。 因此,基于这些发现,针对低CPU线程唤醒延迟的更新推荐的一组引导参数是:

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

使用idle=mwait而不是idle=poll可能也有助于启动Turbo Boost(通过帮助CPU保持在TDP(热devise功耗)以下)和超线程(为此,MWAIT是不消耗整个系统的理想机制物理核心,同时避免更高的C状态)。 这在testing中还没有被证实,但是,我将继续这样做。

更新2:

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

idle=halt – 应该和mwait一样mwait ,但是要testing一下硬件是否如此。 HLT指令几乎等同于状态提示为0的MWAIT 。问题在于中断需要退出HLT状态,而存储器写入(或中断)可用于退出MWAIT州。 根据Linux内核在其空闲循环中使用的内容,这可以使MWAIT潜在地更高效。 所以,正如我所说的testing/configuration文件,看看它是否满足您的延迟需求…

idle=poll – 性能最高的选项,牺牲功率和热量。

也许慢一点是futex,条件variables的基石。 这将揭示一些光明:

 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 

这将显示有趣的系统调用采取的微秒,按时间sorting。

在内核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年前的bug报告包含了一个“乒乓”性能testing

  1. 单线程libpthread互斥
  2. libpthread条件variables
  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. cv 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. cv 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之间的上下文切换确实已经减慢了,尽pipe没有你在内核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. cv 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. cv 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. cv 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. cv 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. $ 

由于独立于c状态的pstate驱动程序,您可能还会看到处理器在更新的进程和Linux内核中单击。 所以除此之外,要禁用这个,你需要下面的内核参数:

intel_pstate=disable