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

时间:2015-04-02 20:40:39

标签: linux multithreading performance scheduler

我已经在多个地方读过Linux的默认调度程序在多核计算机上超线程识别,这意味着如果你有一台拥有2个真实核心的计算机(4 HT) ),它不会将两个忙线程安排到逻辑核心上,使它们在相同的物理内核上运行(在许多情况下会导致2倍的性能成本)。

但是当我在我的Intel 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'找到 HT->物理核心分配。

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


注意:

  • 这个问题与这个other question非常相似,答案就是说 Linux有一个非常复杂的线程调度程序,它具有HT意识。如上所述,我无法观察到这一事实(用stress -c检查自己),并想知道原因。
  • 我知道我可以为我的程序手动设置处理器关联,例如使用taskset工具或sched_setaffinity功能。这不是我想要的,我希望调度程序能够自己知道将两个忙线程映射到一个物理内核并将一个物理内核完全清空是个不错的主意。
  • 我知道有some situations您希望将线程安排到同一个物理核心上并让其他核心空闲,但调度程序大致可以做到这一点似乎毫无意义/ 4个案件。在我看来,它选择的HT核心是完全随机的,或者可能是那些在调度时活动最少的HT核心,但这并不是非常容易超线程的,因为具有如何清晰的程序具有以下特征: stress可以在单独的物理核心上运行。

3 个答案:

答案 0 :(得分:7)

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

Linux调度程序感知超级线程 - 有关它的信息应该从BIOS / UEFI提供的ACPI SRAT / SLIT表中读取 - 而不是从Linux构建scheduler domains

域具有层次结构 - 即在2 CPU服务器上,您将获得三层域: all-cpus,per-cpu-package per-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_balance()。然后检查   parent sched_domain(如果存在),以及父级的父级等   类推。

     

最初,load_balance()找到当前sched域中最繁忙的组。   如果成功,它会查找所有CPU的最繁忙的运行队列&#39;运行中的   那个小组。如果它设法找到这样的runqueue,它会锁定我们的初始值   CPU的runqueue和新发现的最繁忙的runqueue并开始从中移动任务   到我们的runqueue。确切的任务数量之前相当于不平衡   迭代这个sched域的组时计算。

来自: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数据)

答案 1 :(得分:4)

我无法在3.13.0-48上使用我的英特尔(R)Xeon(R)CPU E5-1650 0 @ 3.20GHz重现这一点。

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

以下是两列top stress -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, 

答案 2 :(得分:-2)

使用两个似乎正常工作的额外处理器,i7-2600和Xeon E5-1620,引用您的体验;这可能是一个长镜头,但CPU微码更新怎么样?如果它的内部CPU行为可能包括解决问题的东西。

Intel CPU Microcode下载:http://intel.ly/1aku6ak

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