无意识上下文切换的原因

时间:2013-06-23 23:35:46

标签: c++ multithreading performance profiling context-switch

我正在尝试分析我在一台大型机器(32核,256GB RAM)上编写的多线程程序。我注意到在运行之间,程序的性能可能会有很大差异(70-80%)。我似乎无法找到程序性能中这种巨大差异的原因,但通过分析大量运行中“时间”效用的结果,我注意到非自愿上下文切换的数量与程序性能(显然,较少的上下文切换导致更好的性能,反之亦然)。

有没有什么好方法可以确定导致此上下文切换的原因?如果我能发现罪魁祸首,那么也许我可以尝试解决问题。但是,我对可以使用的工具有一些特殊的限制。首先,我没有机器上的root权限,因此需要这些权限的任何工具都没有。其次,它是一个相当老的内核(RHEL5,内核2.6.18),因此可能不存在一些标准的perf事件。无论如何,关于如何更深入地了解这种情境转换的原因的任何建议将不胜感激。

更新:我决定在另一台(和更小的)机器上测试我的程序。另一台机器是一个带有8Gb RAM的4核(带有高速)linux盒子,另一台机器上有一个更新的内核--- 3.2.0和2.6.18。在新机器上,我无法重现双模式性能配置文件。这让我相信这个问题要么是由于硬件问题(如评论中所建议的),要么是内核级别的特定病理情况,这个问题已经得到修复。我目前最好的假设是,它可能是因为新机器具有完全公平调度程序(CFS)的内核而旧机器没有。有没有办法测试这个假设(告诉新机器使用不同的/旧的调度程序)而不必重新编译新机器的古老内核版本?

6 个答案:

答案 0 :(得分:7)

你提到有32个内核,但硬件的确切布局是什么?例如。机器有多少个包,有多少个核心,如何共享缓存等。为了分享这类信息,我个人喜欢分享likwid-topology -g的输出。

无论如何,你的运行中有一个非确定性:线程亲和力。操作系统分配SW线程以某种方式在特定的HW线程上运行,而不考虑线程如何通信的知识(仅仅因为它没有那些知识)。这可能会导致各种影响,因此对于可重现的运行,最好确保以某种方式将SW线程固定到HW线程(也可能有最佳方式,但到目前为止我我只是在谈论决定论。)

对于固定(也称为 affinity ),您可以使用显式Pthread调用,也可以尝试使用名为likwid-pin的Likwid套件中的其他工具 - 请参阅here

如果这不能获得一致的结果,请在工作负载上运行一个好的分析器(例如Intel VTune),确保捕获更快的运行和更慢的运行,然后比较结果。在VTune中,您可以使用并排显示两个配置文件的比较功能。

答案 1 :(得分:1)

您可以使用ftrace(使用sched_switch跟踪器)跟踪内核中的上下文切换源。此外,使用perf可以帮助您缩小其他指标(缓存未命中等)。

当您将程序从1个线程增加到32个(并且可能超出该程序)时,性能可变性如何变化?

您的程序是否拥有各个线程争用的共享数据(或其他资源)?这样,当有更多的并行执行(因此可变性能)时,它们会遇到更大的竞争条件?除了CPU时间,你的程序的线程会争夺哪些资源?

我认为你必须在你的4核机器上编译旧内核。如果你还没有这样做,那么这样做不仅是一次很好的教育练习,而且在隔离这个问题中的变量方面也很有价值。如果你这样做,我也会尝试匹配确切的发行版,因为你的问题可能不仅限于内核。它也可能是用户空间中的内容,或者可能只是内核的编译选项(因此可以在相同的发行版中匹配)。

您可能希望使用分析器,例如gprof。它可以让您深入了解(潜在的)瓶颈。例如,如果您正在等待写入系统调用或那种性质的东西。

以下是一些可能有助于引发一两个想法的文章:

http://halobates.de/lk09-scalability.pdf

http://pdos.csail.mit.edu/papers/linux:osdi10.pdf

其他来源:Why one non-voluntary context switch per second?

答案 2 :(得分:1)

我认为你的问题实际上是一个调度问题。

一个人不能避免一个人的进程被抢占CPU,但问题是如果一个线程被抢占,然后在它上面,下一个量子会在另一个CPU上结束,或者更具体地在一个CPU上一个不同的L2缓存,然后它对内存的访问将产生缓存未命中并导致数据从内存中获取。另一方面,如果线程在同一个CPU上进行调度,那么很可能它的数据仍然可用于cahce,例如产生更快的内存访问。

请注意,当您拥有越来越多的内核时,最有可能发生此行为。而且由于它是一种“随机”,你的线程最终会在它的下一个量子上,然后这将解释性能的随机性。

有一些分析工具可以让您注册线程的调度位置,例如perf for Linux。通常,这些工具特定于您执行程序的拓扑。不幸的是,现在还没有其他人想到我。还有一些方法可以告诉操作系统在相同(或相邻)的CPU上调度线程,这样他们就可以从更少的缓存未命中中受益。为此,您可以查看this SO question

我建议您向管理员询问您使用哪些工具,这样您就可以对线程安排进行适当的分析和分配。

答案 3 :(得分:1)

您提到的是双模式性能配置文件,您可以在一台计算机上看到,而不是在另一台计算机上看到。这很糟糕,但这是正常的,即使对于单线程应用程序也是如此。

问题在于Linux系统(任何内核,无论使用哪种调度程序)都有太多因素影响应用程序性能。它始于地址随机化,并以微观时序差异结束,升级到进程之间的巨大上下文切换延迟。

Linux不是一个实时系统。它只是试图在平均情况下尽可能高效。

您可以采取一些措施来最大限度地降低性能差异:

将线程数减少到最低限度。不要将问题的不同方面分解为线程。在真正需要的时候分成线程,例如为CPU提供独立的(!)数字运算工作。尝试在一个线程中尽可能多地进行因果关联工作。您的线程应尽可能少地相互通信。特别是在延迟累加的线程之间不应该有任何请求/响应模式。

假设您的操作系统每秒只能在线程/进程之间进行大约1000次上下文切换。这意味着每秒发生几次100个请求/响应事务。如果你在Linux上做基准测试,你发现你可以做更多的事情,请忽略它。

尝试减少重要数据的内存占用。分布式数据往往会对缓存产生非常微妙且难以解释的性能影响。

答案 4 :(得分:0)

在多线程程序中,将线程附加到特定的CPU编号,并检查是否看到性能有所改善。

答案 5 :(得分:0)

在重新安排时,最好的办法是使用内核分析器/记录器Ftrace。这应该显示其他线程正在抢占哪些线程。不幸的是,下半部分中断处理程序没有正确标记,因此很难解读这些。