多核+超线程 - 线程是如何分布的?

时间:2008-12-11 18:07:22

标签: multithreading operating-system multicore hyperthreading

我正在阅读新的英特尔凌动330的评论,他们注意到任务管理器显示4个核心 - 两个物理核心,另外还有两个由超线程模拟。

假设您有一个包含两个线程的程序。假设这些是在PC上进行任何工作的唯一线程,其他一切都是空闲的。操作系统将两个线程放在同一个核心上的概率是多少?这对程序吞吐量有很大影响。

如果答案不是0%,那么除了创建更多线程之外,还有其他任何缓解策略吗?

我预计Windows,Linux和Mac OS X会有不同的答案。

<小时/> 使用sk's answer作为谷歌饲料,然后按照链接,我在Windows中找到了GetLogicalProcessorInformation功能。它说的是“共享资源的逻辑处理器。这种资源共享的一个例子就是超线程场景。”这意味着jalf是正确的,但它不是一个明确的答案。

8 个答案:

答案 0 :(得分:8)

Linux拥有非常复杂的线程调度程序,可以识别HT。其中一些策略包括:

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

Active Loadbalancing:如果有3个任务,一个物理cpu上有2个,另一个物理处理器空闲时有1个,调度程序将尝试将其中一个任务迁移到它。

它在尝试保持线程亲和性时执行此操作,因为当线程迁移到另一个物理处理器时,它必须从主内存重新填充所有级别的缓存,从而导致任务停顿。

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

答案 1 :(得分:5)

理智的操作系统会尝试在自己的核心上安排计算密集型任务,但是当您启动上下文切换时会出现问题。现代操作系统仍然倾向于在核心上安排事情,而在调度时没有工作,但这可能导致并行应用程序中的进程相当自由地从核心交换到核心。对于并行应用程序,您不希望这样,因为您丢失了该进程可能在其核心的高速缓存中使用的数据。人们使用处理器亲和力来控制它,但在Linux上,sched_affinity()的语义在发行版/内核/供应商等之间可能会有很大差异。

如果您使用的是Linux,则可以使用Portable Linux Processor Affinity Library (PLPA)轻松控制处理器关联。这是OpenMPI内部使用的,以确保进程在多核和多插槽系统中安排到自己的内核;他们只是将模块拆分为独立项目。 OpenMPI在洛斯阿拉莫斯的许多其他地方使用,因此这是经过良好测试的代码。我不确定Windows下的等价物是什么。

答案 2 :(得分: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());
        }
    }
}

答案 3 :(得分:3)

操作系统不会使用尽可能多的物理内核的概率基本上为0%。你的操作系统并不愚蠢。它的工作是安排一切,它完全清楚它有哪些核心。如果它看到两个CPU密集型线程,它将确保它们在两个物理内核上运行。

修改的 只是详细说明,对于高性能的东西,一旦进入MPI或其他严重的并行化框架,你肯定想要控制每个核心上运行的东西。

操作系统将尽最大努力尝试利用所有内核,但它没有您所做的长期信息,“此线程将运行很长时间”,或者“我们将要让这么多线程并行执行”。因此,它无法做出完美的决定,这意味着您的线程将不时被分配到新的核心,这意味着您将遇到缓存未命中等等,这需要花费一些时间。在大多数情况下,它足够好,你甚至不会注意到性能差异。如果这很重要的话,它对系统的其他部分也很有用。 (在某人的桌面系统上,这可能相当重要。在一个有几千个专用于此任务的CPU的网格中,你不是特别想要玩得很好,你只想使用每个可用的时钟周期。)

因此,对于大规模的HPC,是的,你会希望每个线程都保留在一个核心上,修复。但对于大多数较小的任务来说,它并不重要,您可以信任操作系统的调度程序。

答案 4 :(得分:3)

这是一个非常好的相关问题。众所周知,超线程核心不是真正的CPU /核心。相反,它是一个虚拟CPU /核心(从现在开始,我会说核心)。从Windows XP开始的Windows CPU调度程序应该能够区分超线程(虚拟)内核和实际内核。你可能会想到,在这个完美的世界中,它处理它们“恰到好处”并且不是问题。你错了。

Microsoft自己建议优化Windows 2008 BizTalk服务器建议禁用超线程。对我来说,这表明超线程内核的处理并不完美,有时线程会在超线程内核上获得时间片并受到惩罚(实际内核的性能的一小部分,10%I') d猜测,微软猜测20-30%)。

Microsoft文章参考,他们建议禁用超线程以提高服务器效率:http://msdn.microsoft.com/en-us/library/cc615012(BTS.10).aspx

这是BIOS更新后的第二条建议,这是他们认为重要的。他们说:

来自MICROSOFT:

  

禁用BizTalk上的超线程   服务器和SQL Server计算机

     

关键超线程是   关闭的BizTalk Server   电脑。这是一个BIOS设置,   通常在处理器中找到   BIOS设置的设置。   超线程使服务器   似乎有更多   处理器/处理器核心比它   实际上;但是超线程   处理器通常提供   20和30%的表现   物理处理器/处理器核心。   当BizTalk Server计算数字时   处理器来调整它   自调整算法;该   超线程处理器导致这些   调整是倾斜的   不利于整体表现。 “

现在,他们确实说它是由于它放弃了自我调整算法,但接着提到争用问题(暗示这是一个更大的调度问题,至少对我而言)。按照你的意愿阅读它,但我认为它说明了一切。使用单CPU系统时,超线程是一个好主意,但现在只是一个可能会影响这个多核心世界的性能的复杂功能。

您可以使用Process Lasso(免费)等程序为关键进程设置默认CPU关联,而不是完全禁用HyperThreading,以便永远不会将其线程分配给虚拟CPU。

所以......我认为没有人真正知道Windows CPU Scheduler处理虚拟CPU的能力有多好,但我认为可以说XP处理得最差,并且从那时起它们逐渐改进了它,但它仍然不完美。事实上,它可能永远不会是完美的,因为操作系统不知道什么线程最好放在这些较慢的虚拟核心上。这可能是那里的问题,也是Microsoft建议在服务器环境中禁用超线程的原因。

还记得即使没有超线程,也存在'核心颠簸'的问题。如果你可以在一个核心上保留一个线程,这是一件好事,因为它减少了核心的变化惩罚。

答案 5 :(得分:2)

您可以通过为两个线程提供处理器关联来确保它们为相同的执行单元进行调度。这可以在windows或unix中通过API(因此程序可以请求它)或通过管理界面(因此管理员可以设置它)来完成。例如。在WinXP中,您可以使用任务管理器来限制进程可以执行的逻辑处理器。

否则,调度基本上是随机的,您可以预期每个逻辑处理器的使用率为25%。

答案 6 :(得分:1)

我不了解其他平台,但就英特尔而言,他们在info on threading上发布了大量Intel Software Network。他们还有一个免费的时事通讯(The Intel Software Dispatch),您可以通过电子邮件订阅,最近有很多这样的文章。

答案 7 :(得分:0)

操作系统将2个活动线程分配到同一个核心的机会是,除非线程绑定到特定核心(线程关联)。

背后的原因主要与硬件相关:

  • 操作系统(和CPU)希望尽​​可能少地使用电源,以便尽可能高效地运行任务,以便尽快进入低功耗状态。
  • 在同一个核心上运行所有内容将使其加热更快。在病理条件下,处理器可能会过热并减少其时钟以冷却。过热也会导致CPU风扇旋转得更快(想想笔记本电脑)并产生更多噪音。
  • 系统永远不会空闲。 ISR和DPC每隔ms运行一次(在大多数现代操作系统上)。
  • 在99.99%的工作负载中,由于线程从核心跳到核心而导致的性能下降可以忽略不计。
  • 在所有现代处理器中,共享最后一级缓存,因此交换核心并不是那么糟糕。
  • 对于多插槽系统(Numa),操作系统将最大限度地减少从套接字到套接字的跳跃,以便进程保持“靠近”其内存控制器。在优化此类系统(数十/数百个核心)时,这是一个复杂的领域。

顺便说一句,操作系统知道CPU拓扑的方式是通过ACPI - BIOS提供的接口。

总而言之,这一切都归结为系统功耗考虑因素(电池寿命,电费,冷却解决方案的噪音)。