【问题标题】:Cause of involuntary context switches非自愿上下文切换的原因
【发布时间】:2013-06-20 10:01:01
【问题描述】:

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

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

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

【问题讨论】:

  • “非自愿上下文切换”是指“其他进程想要运行”,还是“我的进程做了一些事情导致系统在系统完成某些工作时停止它,例如等待一些要从磁盘或网络加载的文件数据”?
  • 你知道 pthread_cond_t 吗?
  • @MatsPetersson 是的 - '首先,我在这台机器上没有 root 权限'并不建议独占使用它。
  • 询问系统管理员,谁有权限,找出来。
  • @DanielKO -- time 实用程序实际上通过自愿/非自愿分解上下文切换。这里的非自愿上下文切换的定义是当您的进程由于某种原因被操作系统抢占,而不是它自愿放弃控制(例如让步/等待)。当它的时间片到期并且有更高优先级的进程要运行时,可能会发生这种情况,并且可能在许多其他条件下也是如此。

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


【解决方案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?

【讨论】:

  • 最后,您可以随时申请内核升级。 :-)
  • 感谢您的链接。我认为你是对的,我可能确实不得不为另一台机器重新编译旧内核。
  • 另外,这些视频讲座有很多很好的工具和技术,可能对您有用:ocw.mit.edu/courses/electrical-engineering-and-computer-science/…
  • @nomad 我已经更新了它以引用 ftrace 工具,它可以让您进一步了解上下文切换。
【解决方案2】:

当涉及到重新调度时,最好的办法是使用 Kernel Profiler/logger Ftrace。这应该向您显示其他线程正在抢占哪些线程。不幸的是,下半部中断处理程序没有正确标记,因此很难破译这些。

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

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

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

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

      【讨论】:

      • 感谢您的建议。我实际上尝试使用 pthreads 直接设置我的线程的亲和力(并且它有效),但我仍然看到性能可变性。这让我相信这个问题很可能是旧内核/调度程序的结果。但是,我不知道 likwid 工具,它们似乎非常有用。此外,我已说服管理员创建一个 sudoers 组,以便我可以实际使用这些分析工具。我认为仅凭这个建议可能就值得赏金。
      • 感谢您的赏金!有趣的是,亲和力并没有提高重现性。接下来我会尝试查看(尽管使用分析器)是可变性来自用户时间还是内核时间。仅仅“时间”实用程序就足够了。作为对调度问题的更深入研究,我会使用带有堆栈的高级热点(收集上下文切换信息)或 ftrace 的调度跟踪功能来进行 VTune。我应该问的一件重要的事情是你的工作量需要多长时间。您说的是 80% 的可变性 - 但实际上是 5 秒与 9 秒还是 5 毫秒与 9 毫秒?
      • 可变性在相当大的时间范围内(例如 120 与 200 秒)。如果它只发生在非常高的分辨率下(例如 5 毫秒 vs 9 毫秒),我可能不会那么担心,也不确定这不仅仅是测量错误。
      • 120 秒对 200 秒真的很有趣。您是否对工作负载中的网络带宽有任何依赖?这就像从网络共享中读取大量数据一样棘手。根据网络负载,您可以获得不同的速度。但这不会给你一个双模态的结果。无论如何,当您找到最终原因时,知道它是什么会非常有用 - 如果您有机会,请随时关注我们! :)
      【解决方案5】:

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

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

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

      您可以做很多事情来最小化性能差异:

      将线程数减少到所需的最低限度。不要将问题的不同方面拆分为线程。仅在真正需要时拆分为线程,例如为 CPU 提供独立的(!)数字运算作业。尽量在一个线程中做尽可能多的因果联系工作。您的线程应该尽可能少地相互通信。特别是在延迟加起来的线程之间不应该有请求/响应模式。

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

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

      【讨论】:

        【解决方案6】:

        我相信您的问题实际上是日程安排问题。

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

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

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

        我建议您询问您的管理员您可以使用哪些此类工具,以便您可以对线程调度进行适当的分析和分配。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-03-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-04-04
          • 1970-01-01
          • 2021-04-07
          • 2011-07-23
          相关资源
          最近更新 更多