为什么Linux的调度程序将两个线程放在具有超线程的处理器的相同物理内核上?

我读过多处,Linux的默认调度程序在多核机器上是超线程感知的 ,这意味着如果你有一台拥有2个真实核心(4 HT)的机器,它不会将两个繁忙的线程调度到逻辑核心上他们都运行在相同的物理内核上(这在很多情况下会导致性能成本的2倍)。

但是当我在英特尔i5-2520M上运行stress -c 2 (产生两个线程在100%的CPU上运行)时, 它经常 将两个线程 调度 (并保持) 到HT核心1和2上,这两个线程映射到相同的物理核心 。 即使系统空闲,否则。

这也发生在真正的程序(我在这里使用stress ,因为它使得它很容易重现),当这种情况发生时,我的程序可以理解需要两倍的时间运行。 使用taskset手动设置亲和力可以修复我的程序,但是我希望HT认知调度程序能够自己正确地做到这一点。

您可以使用egrep "processor|physical id|core id" /proc/cpuinfo | sed 's/^processor/\nprocessor/g'findHT->物理核心configurationegrep "processor|physical id|core id" /proc/cpuinfo | sed 's/^processor/\nprocessor/g' egrep "processor|physical id|core id" /proc/cpuinfo | sed 's/^processor/\nprocessor/g'

所以我的问题是:为什么调度程序把我的线程放在同一个物理内核上呢?


笔记:

  • 这个问题与这个问题非常相似,答案就是说Linux有一个非常复杂的HT调度线程调度器 。 如上所述,我无法观察到这个事实(检查自己的stress -c ),并想知道为什么。
  • 我知道我可以为我的程序手动设置处理器关联,例如使用taskset工具或sched_setaffinity函数。 这不是我正在寻找的,我希望调度程序自己知道把两个繁忙的线程映射到一个物理核心并将一个物理核心完全清空并不是一个好主意。
  • 我知道在某些情况下 ,你更喜欢把线程安排在同一个物理核心上,而把另一个核心放在空闲的位置,但是调度程序大概只有1/4的情况似乎是无意义的。 在我看来,它select的HT内核是完全随机的,或者是在调度时活动最less的HT内核,但是这并不是非常了解超线程的,因为考虑到stress特性的程序有多好从单独的物理内核运行。

Solutions Collecting From Web of "为什么Linux的调度程序将两个线程放在具有超线程的处理器的相同物理内核上?"

我认为是时候从评论中总结一些知识了。

Linux调度程序知道 HyperThreading – 应该从BIOS / UEFI提供的ACPI SRAT / SLIT表中读取有关它的信息,而不是从Linux构建调度程序域

域具有层次结构 – 即在2-CPU服务器上,您将获得三层域: all-cpus,per-cpu-packageper-cpu-core域。 你可以从/proc/schedstat检查它:

 $ awk '/^domain/ { print $1, $2; } /^cpu/ { print $1; }' /proc/schedstat cpu0 domain0 0000,00001001 <-- all cpus from core 0 domain1 0000,00555555 <-- all cpus from package 0 domain2 0000,00ffffff <-- all cpus in the system 

CFS调度程序的一部分是负载平衡器(load balancer) – 应该将繁忙内核中的任务从另一个核心中移出的野兽。 以下是内核文档中的描述:

在这样做的时候,它会检查当前域是否已经耗尽了重新平衡时间间隔。 如果是这样,它在该域上运行load_balance() 。 然后检查父sched_domain(如果存在)以及父级的父级等等。

最初, load_balance()查找当前调度域中最繁忙的组。 如果成功,它将查找该组中所有CPU运行队列中最繁忙的运行队列。 如果设法找到这样一个runqueue,它就会锁定我们最初的CPU runqueue和新发现的最忙的runqueue,并开始将任务从它移动到runqueue。 任务的确切数量相当于先前计算的迭代在这个调度域组上的不平衡。

来自: https : //www.kernel.org/doc/Documentation/scheduler/sched-domains.txt

您可以通过比较/proc/schedstat数字来监视负载均衡器的活动。 我为此写了一个脚本: schedstat.py

计数器alb_pushed显示负载平衡器已成功移出任务:

 Sun Apr 12 14:15:52 2015 cpu0 cpu1 ... cpu6 cpu7 cpu8 cpu9 cpu10 ... .domain1.alb_count ... 1 1 1 .domain1.alb_pushed ... 1 1 1 .domain2.alb_count 1 ... .domain2.alb_pushed 1 ... 

然而,负载平衡器的逻辑复杂,所以很难确定什么原因可以阻止它的工作,以及它们如何与schedstat计数器相关联。 我和@thatotherguy都不能重现你的问题。

我看到了这种行为的两种可能性:

  • 你有一些积极的节能政策,试图节省一个核心,以降低CPU的功耗。
  • 你真的遇到了一个计划子系统的错误,比你应该去LKML ,仔细分享你的发现(包括mpstatschedstat数据)

我无法在3.13.0-48上用我的Intel(R)Xeon(R)CPU E5-1650 0 @ 3.20GHz重现此功能。

我有6个超线程核心,其中逻辑核心N映射到物理核心N mod 6。

这是一个典型的输出topstress -c 4两列,以便每行是一个物理核心(我遗漏了几个核心,因为我的系统不闲置):

 %Cpu0 :100.0 us, %Cpu6 : 0.0 us, %Cpu1 :100.0 us, %Cpu7 : 0.0 us, %Cpu2 : 5.9 us, %Cpu8 : 2.0 us, %Cpu3 :100.0 us, %Cpu9 : 5.7 us, %Cpu4 : 3.9 us, %Cpu10 : 3.8 us, %Cpu5 : 0.0 us, %Cpu11 :100.0 us, 

这是在杀害和重新开始stress

 %Cpu0 :100.0 us, %Cpu6 : 2.6 us, %Cpu1 :100.0 us, %Cpu7 : 0.0 us, %Cpu2 : 0.0 us, %Cpu8 : 0.0 us, %Cpu3 : 2.6 us, %Cpu9 : 0.0 us, %Cpu4 : 0.0 us, %Cpu10 :100.0 us, %Cpu5 : 2.6 us, %Cpu11 :100.0 us, 

我多次这样做,并没有看到12个逻辑核心中的4个线程将安排在相同的物理核心上的任何实例。

使用-c 6我倾向于得到像这样的结果,其中Linux似乎有助于在自己的物理内核上安排其他进程。 即使如此,他们分发的方式比机会更好:

 %Cpu0 : 18.2 us, %Cpu6 : 4.5 us, %Cpu1 : 0.0 us, %Cpu7 :100.0 us, %Cpu2 :100.0 us, %Cpu8 :100.0 us, %Cpu3 :100.0 us, %Cpu9 : 0.0 us, %Cpu4 :100.0 us, %Cpu10 : 0.0 us, %Cpu5 :100.0 us, %Cpu11 : 0.0 us, 

用两个额外的处理器引用你的经验,似乎工作正常,i7-2600和Xeon E5-1620; 这可能是一个长期的,但如何CPU微码更新? 如果是内部CPU行为,可能会包含一些解决问题的方法。

英特尔CPU微码下载: http : //intel.ly/1aku6ak

另见这里: https : //wiki.archlinux.org/index.php/Microcode