多核+超线程-线程如何分配?

27
我正在阅读新的Intel Atom 330评测,其中他们指出任务管理器显示4个内核-两个物理内核,加上另外两个通过超线程模拟的内核。
假设您有一个有两个线程的程序。同时也假设这些是在PC上执行任何工作的唯一线程,其他所有内容都处于空闲状态。操作系统将两个线程放在同一个内核上的概率是多少?这对程序吞吐量有巨大的影响。
如果答案不是0%,除了创建更多线程之外,是否有任何缓解策略?
我预计Windows,Linux和Mac OS X会有不同的答案。
使用sk的回答作为Google搜索关键词,然后跟随链接,我发现了Windows中的GetLogicalProcessorInformation函数。它提到“共享资源的逻辑处理器。此类资源共享的示例将是超线程场景。”这意味着jalf是正确的,但这并不是一个明确的答案。

2
我想评论一下,最优策略并不总是在不同的核心上运行两个任务;例如,如果您有两个共享内存并执行许多非重叠操作的任务,则在同一核心上运行它们可能会提供更高的性能,因为缓存未命中的减少抵消了偶尔必须共享处理器的稍微较慢的运行时间(请记住,在这种情况下,即使在一个核心上,这两个线程通常也会并行运行,因为它们使用不同的逻辑单元)。 - Borealid
只是提醒一下:如果你追求原始性能,可能需要禁用超线程。除非英特尔已经使其正常工作。在过去(我最后一次测试是在一个带有超线程的2x处理器P4 Xeon盒子上,向操作系统提供了4个逻辑处理器),启用超线程运行4个计算密集型线程的净性能比禁用超线程运行2个线程的净性能要低。显然,你需要使用最新的硬件进行测试——这可能不再是这种情况。但是,请注意... - Nathan Ernst
2
在某些情况下,将线程运行在同一核心上是完全符合要求的。例如,在运行无锁数据结构时;当您有不同物理核心上的线程时,核间缓存行交换会导致性能急剧下降。 - user82238
8个回答

9

Linux拥有相当复杂的线程调度程序,支持超线程技术。其中一些策略包括:

被动负载平衡:如果一个物理CPU正在运行多个任务,调度程序将尝试在第二个物理处理器上运行任何新任务。

主动负载平衡:如果有3个任务,2个在一个物理CPU上,1个在另一个物理CPU上,当第二个物理处理器空闲时,调度程序将尝试将其中一个任务迁移到它上面。

它这样做是为了保持线程亲和性,因为当一个线程迁移到另一个物理处理器时,它将不得不从主内存重新填充所有级别的缓存,从而导致任务停顿。

所以回答你的问题(至少在Linux上):在双核超线程机器上给定2个线程,每个线程将在自己的物理核心上运行。


我在我的机器上没有看到这种情况发生。在我的i5-2520M上运行stress -c 2,有时会将两个线程调度(并保持)到HT核心1和2上,这些核心映射到同一个物理核心。即使系统其他方面处于空闲状态。(我使用egrep“processor | physical id | core id”/ proc / cpuinfo | sed's / ^ processor / \ nprocessor / g'找到了HT->物理核心分配。) - nh2
我通过这个问题使这个问题更具体。 - nh2

5
一款健全的操作系统会尝试在其自身核心上安排计算密集型任务,但当您开始切换上下文时,问题就会出现。现代操作系统仍然有倾向于在没有工作的核心上安排任务,但这可能导致并行应用程序中的进程相对自由地从一个核心交换到另一个核心。对于并行应用程序,您不希望出现这种情况,因为它会丢失进程可能在其核心缓存中使用的数据。人们使用处理器亲和力来控制这一点,但在Linux上,sched_affinity()的语义在发行版/内核/供应商之间可能会有很大差异。
如果您正在使用Linux,则可以通过Portable Linux Processor Affinity Library (PLPA)轻松控制处理器亲和力。这是OpenMPI在多核和多插座系统中确保进程被安排到自己的核心上所使用的内部模块;他们只是将该模块作为独立项目推出。OpenMPI在洛斯阿拉莫斯等许多其他地方都得到了使用,因此这是经过充分测试的代码。我不确定Windows下的等效物是什么。

5
我一直在寻找关于Windows线程调度的答案,并有一些经验性信息将在此发布,供未来可能会遇到此帖子的任何人参考。
我编写了一个简单的C#程序,启动了两个线程。在我的四核Windows 7电脑上,我看到了一些令人惊讶的结果。
当我没有强制指定处理器亲和性时,Windows将这两个线程的工作负载分配到了所有四个核心上。有两行被注释掉的代码 - 一个绑定线程到CPU的,一个建议优先使用的CPU。虽然不知道建议是否起作用,但设置线程亲和性确实导致Windows在自己的核心上运行每个线程。
为了最好地查看结果,请使用.NET Framework 4.0客户端附带的免费编译器csc.exe编译此代码,并在具有多个核心的计算机上运行它。在处理器亲和性行被注释掉的情况下,任务管理器显示线程分布在所有四个核心上,每个核心的运行速度约为50%。设置了亲和性后,两个线程占用了两个核心的100%,另外两个核心处于空闲状态(这是我在运行此测试之前预期要看到的结果)。
编辑: 我最初发现这两个配置存在一些性能差异。然而,我现在无法重现它们,所以我编辑了此帖子以反映这一点。尽管如此,线程亲和性仍然让我感到有趣,因为这不是我预期的结果。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

class Program
{
    [DllImport("kernel32")]
    static extern int GetCurrentThreadId();

    static void Main(string[] args)
    {
        Task task1 = Task.Factory.StartNew(() => ThreadFunc(1));
        Task task2 = Task.Factory.StartNew(() => ThreadFunc(2));
        Stopwatch time = Stopwatch.StartNew();
        Task.WaitAll(task1, task2);
        Console.WriteLine(time.Elapsed);
    }

    static void ThreadFunc(int cpu)
    {
        int cur = GetCurrentThreadId();
        var me = Process.GetCurrentProcess().Threads.Cast<ProcessThread>().Where(t => t.Id == cur).Single();
        //me.ProcessorAffinity = (IntPtr)cpu;     //using this line of code binds a thread to each core
        //me.IdealProcessor = cpu;                //seems to have no effect

        //do some CPU / memory bound work
        List<int> ls = new List<int>();
        ls.Add(10);
        for (int j = 1; j != 30000; ++j)
        {
            ls.Add((int)ls.Average());
        }
    }
}

1
你应该意识到,如果你使用任务管理器查看使用情况,任务管理器本身可能对系统有很大的干扰,因为它通常以提高的优先级运行。尝试将任务管理器强制设置为低优先级,看看模式是否会改变。 - Zan Lynx
你能分享一下在不同配置下的运行时间吗? - Mark Ransom

3
这是一个非常好而相关的问题。我们都知道,超线程核心不是真正的CPU/核心,而是虚拟的CPU/核心(从现在开始我将说核心)。Windows XP的CPU调度程序应该能够区分超线程(虚拟)核心和真正的核心。你可能会想象,在这个完美的世界中,它会"恰如其分"地处理它们,并且这不是一个问题。但你会错的。
微软自己优化Windows 2008 BizTalk服务器的建议是禁用HyperThreading。这表明,对于超线程核心的处理并不完美,有时线程会在一个超线程核心上获得时间片并遭受惩罚(相对于真正的核心性能的一小部分,我猜10%,微软猜测20-30%)。
微软建议禁用HyperThreading以提高服务器效率的文章参考链接:http://msdn.microsoft.com/en-us/library/cc615012(BTS.10).aspx 这是BIOS更新之后的第二个建议,这就是他们认为它有多么重要的原因。他们说:
“在BizTalk服务器和SQL Server计算机上禁用超线程 对于BizTalk服务器计算机,关闭超线程至关重要。这是一个BIOS设置,通常可以在BIOS设置的处理器设置中找到。超线程使服务器看起来比实际拥有更多的处理器/处理器核心;然而,超线程处理器通常只提供物理处理器/处理器核心性能的20%至30%。当BizTalk服务器计算处理器数量以调整其自我调整算法时,超线程处理器会导致这些调整偏差,这对总体性能有害。” “现在,他们确实说这是由于它扰乱了自我调整算法,但接着又提到了争用问题(至少对我来说表明这是一个更大的调度问题)。按您的方式阅读它,但我认为它已经说明了一切。在单CPU系统中,超线程是一个好主意,但现在却是一个可能损害性能的复杂问题。”

与其完全禁用超线程,您可以使用像Process Lasso(免费)这样的程序为关键进程设置默认CPU亲和性,以便它们的线程永远不会分配到虚拟CPU。

所以...我认为没有人真正知道Windows CPU调度程序如何处理虚拟CPU,但我认为可以肯定的是,XP处理得最差,自那时以来逐渐改进了,但仍然不完美。实际上,它可能永远不会完美,因为操作系统不知道将哪些线程放在这些较慢的虚拟核心上最好。这可能是问题所在,也是微软建议在服务器环境中禁用超线程的原因。

还要记住,即使没有超线程,也存在“内核抖动”的问题。如果您可以将线程保留在单个核心上,则可以减少内核更换惩罚。


3
概率基本上是0%,操作系统不会利用尽可能多的物理核心。您的操作系统并不愚蠢。它的工作是调度所有任务,并且完全知道可用的核心数量。如果它看到两个需要大量CPU的线程,它将确保它们在两个物理核心上运行。
编辑一下,对于高性能的东西,一旦你进入MPI或其他严格的并行化框架,你肯定想控制每个核心上运行的内容。
操作系统会尽最大努力利用所有核心,但它没有您拥有的长期信息,“这个线程将运行很长时间”,或者“我们将有这么多线程并行执行”。因此,它不能做出完美的决策,这意味着您的线程将不时被分配到新的核心,这意味着您将遇到缓存未命中等问题,这会花费一些时间。对于大多数目的而言,这已经足够好了,您甚至不会注意到性能差异。而且它还与系统的其余部分相容,如果这很重要的话。(对于某人的桌面系统来说,这可能非常重要。在一个专门为此任务配备了几千个CPU的网格中,您不特别想表现得很好,您只想利用每个可用的时钟周期)。
因此,对于大规模的HPC任务,是的,您需要让每个线程保持在一个固定的核心上。但对于大多数较小的任务而言,这并不重要,您可以信任操作系统的调度程序。

我也想这样相信,但有一些证据会更有用。 - Mark Ransom
证据是什么?创建一个程序,运行两个线程在无限循环中,并检查CPU使用率。你会发现任何明智的操作系统都会将一个线程分配给每个核心。你认为这是操作系统设计师没有考虑到的问题吗?当然不是。这是一个操作系统必须处理的基本问题。 - jalf
如果它检测到两个CPU密集型线程,它会确保它们在两个物理核心上运行。但这并不总是最优的选择。如果这些线程操作相同的内存,它们在同一物理核心上运行将会极大地受益。此外,CPU密集型和缓存使用密集型之间存在差异。如果这些线程正在大量利用缓存并且正在操作不同的内存,则它们从分别运行在不同的核心中受益(也就是说,实际上是在不同的缓存中运行)。 - user82238
@Blank:是的?这与我说的有什么矛盾之处?我在哪里声称操作系统会进行最优调度?除此之外,我必须说我对你的“巨大”说法持怀疑态度。除非你强制将操作系统过于频繁地进行上下文切换(在这种情况下,你会有更严重的问题),否则线程将在同一核心上运行足够长的时间,以使当线程移动到不同的核心时,由缓存未命中引起的性能损失相当小。你不会仅通过强制线程亲和力就看到性能提高三倍。 - jalf
1
@Jalf:我所考虑的“极大”使用案例是无锁数据结构。一旦开始在不同的物理核心上运行,性能就会急剧下降——所有缓存行交换,因为每个CAS写入都会使其他物理核心的缓存行失效。上下文切换不是问题。 - user82238
显示剩余4条评论

2
你可以通过给它们处理器亲和力来确保两个线程被安排在相同的执行单元上。这可以通过API(使程序可以请求)或管理接口(使管理员可以设置)在Windows或Unix中完成。例如,在WinXP中,你可以使用任务管理器限制进程可以在哪个逻辑处理器上执行。
否则,调度将基本上是随机的,你可以预期每个逻辑处理器的使用率为25%。

虽然我从来不喜欢把事情留给操作系统,但如果事情变得繁忙,设置线程亲和力掩码可能会对性能产生负面影响。使用SetThreadIdealProcessor()是否是更好的选择? - NTDLS

1

我不知道其他平台的情况,但在英特尔的情况下,他们在Intel软件网络上发布了很多关于线程的信息。他们还有一份免费的电子通讯(英特尔软件调度),您可以通过电子邮件订阅,并且最近有很多这样的文章。


0
操作系统将两个活动线程分派到同一个核心的机会是,除非将这些线程绑定到特定核心(线程亲和性)。
这背后的原因主要与硬件有关:
  • 操作系统(和CPU)希望尽可能少地使用电源,因此它将尽可能高效地运行任务,以便尽快进入低功耗状态。
  • 在同一核心上运行所有任务会导致其加热更快。在病态条件下,处理器可能会过热并降低时钟速度以进行冷却。过高的温度也会导致CPU风扇旋转更快(如笔记本电脑),制造更多噪音。
  • 系统从未真正处于空闲状态。中断服务例程(ISRs)和延迟过程调用(DPCs)每毫秒运行一次(在大多数现代操作系统中)。
  • 由于线程在不同核心之间跳跃导致的性能降级在99.99%的工作负载中都可以忽略不计。
  • 在所有现代处理器中,最后一级缓存是共享的,因此切换核心并不是太糟糕。
  • 对于多插槽系统(Numa),操作系统将最小化从插槽到插槽的跳跃,使进程保持“靠近”其内存控制器。当为这些系统进行优化时(十几个/数百个核心),这是一个复杂的领域。

顺便说一下,操作系统了解CPU拓扑结构的方式是通过ACPI - BIOS提供的接口。

总的来说,这一切归结于系统功率考虑(电池寿命、电费、冷却解决方案的噪音)。

我并不是在询问为什么它不应该这样做的原因列表,我认为我们都可以达成共识。我想知道操作系统是否有足够的信息来防止它,并且调度程序是否足够聪明以使用这些信息。你回答中唯一相关的部分是提到了ACPI。 - Mark Ransom
我的回答提供了调度程序行为的“为什么”和“如何”,以及它们是否具有此信息。您是否正在寻找内核代码片段作为答案?如果是这样,Linux 和 Darwin 内核都是开源的... - egur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接