非自愿上下文切换的原因

Cause of involuntary context switches

本文关键字:上下文切换      更新时间:2023-10-16

我正试图评测我在一台稍大的机器(32核,256GB RAM)上编写的多线程程序。我注意到,在不同的运行之间,程序的性能可能会有很大的差异(70-80%)。我似乎找不到程序性能出现这种巨大差异的原因,但通过分析"时间"工具在大量运行中的结果,我注意到非自愿上下文切换的数量与程序性能高度相关(显然,上下文切换越少,性能越好,反之亦然)。

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

更新:我决定在另一台(更小的)机器上测试我的程序。另一台机器是一个4核(带超标题)的linux盒子,有8Gb的RAM,另一台计算机上有一个更新得多的内核——3.2.0比2.6.18。在新机器上,我无法复制双模性能配置文件。这让我相信,这个问题要么是由于硬件问题(正如评论中所建议的),要么是由于内核级别的一个特别病态的案例,该案例已经修复。我目前最好的假设是,这可能是因为新机器有一个带有完全公平调度器(CFS)的内核,而旧机器没有。有没有一种方法可以测试这个假设(告诉新机器使用不同/旧的调度器),而不必为新机器重新编译旧的内核版本?

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

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

对于固定(也称为亲和性),您可以使用显式Pthread调用,也可以尝试Likwid套件中名为likwid-pin的另一个工具-请参阅此处。

如果无法获得一致的结果,请在工作负载上运行一个好的探查器(如"英特尔VTune"),确保捕获更快的运行和更慢的运行,然后比较结果。在VTune中,您可以使用并排显示两个配置文件的比较功能。

我认为您的问题实际上是日程安排问题。

我们无法避免一个进程被CPU抢占,但问题是,如果一个线程被抢占,然后在它的下一个量子点上,最终在另一个CPU上,或者更具体地说,在具有不同二级缓存的CPU上,那么它对内存的访问都会产生缓存未命中,并导致数据从内存中提取。另一方面,如果线程被调度在同一CPU上,那么它的数据很可能在cahce上仍然可用,例如,产生更快的内存访问。

请注意,当您拥有越来越多的核心时,这种行为最有可能发生。由于这是一种"随机",你的线程将在它的下一个量子上结束,那么这将解释性能的随机性。

有一些评测工具允许您注册线程的调度位置,例如perf for Linux。通常,这些工具对于执行程序的拓扑结构是特定的。不幸的是,我现在没有想到其他事情。还有一些方法可以告诉操作系统在相同(或adyacent)的CPU上调度线程,这样它们将从更少的缓存未命中中受益。为此,你可以查看这个SO问题

我建议你问你的管理员,你可以使用哪种工具,这样你就可以对线程调度进行适当的分析和分配。

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

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

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

为了最大限度地减少性能差异,可以做很多事情:

将线程数量减少到所需的最低限度。不要把问题的不同方面分解成几个线程。只要在真正需要的时候将其拆分为线程,例如为CPU提供独立的(!)数字运算作业。尽量在一个线程中完成尽可能多的因果连接工作。线程之间应该需要尽可能少的通信。特别是,在延迟增加的线程之间不应该有请求/响应模式。

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

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

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

当您将程序从1个线程增加到32个线程(甚至可能超过32个线程)时,性能变化是如何变化的?

可能是您的程序共享了各个线程正在争夺的数据(或其他资源)吗?这样,当有更多的并行执行时,它们会经历更大的竞争条件(因此会产生可变性能)?除了CPU时间,程序的线程还争夺什么资源?

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

您可能需要研究使用探查器,例如gprof。它可能会让你深入了解(潜在的)瓶颈。例如,如果您正在等待写系统调用或类似性质的调用。

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

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

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

其他来源:为什么每秒有一个非自愿的上下文切换?

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

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