我正在阅读新的英特尔凌动330的评论,他们注意到任务管理器显示4个核心 - 两个物理核心,另外两个由超线程模拟.
假设你有一个包含两个线程的程序.假设这些是在PC上进行任何工作的唯一线程,其他一切都是空闲的.操作系统将两个线程放在同一个核心上的概率是多少?这对程序吞吐量有很大影响.
如果答案不是0%,那么除了创建更多线程之外,是否还有其他缓解策略?
我希望Windows,Linux和Mac OS X会有不同的答案.
Linux具有非常复杂的线程调度程序,可以识别HT.其中一些策略包括:
被动负载平衡:如果物理CPU运行多个任务,则调度程序将尝试在第二个物理处理器上运行任何新任务.
主动负载均衡:如果有3个任务,当第二个物理处理器空闲时,一个物理cpu上有2个,另一个上有1个,调度程序将尝试将其中一个任务迁移到它.
它在尝试保持线程亲和性时执行此操作,因为当线程迁移到另一个物理处理器时,它将不得不从主内存重新填充所有级别的缓存,从而导致任务停顿.
所以回答你的问题(至少在Linux上); 在双核超线程机器上给出2个线程,每个线程将在其自己的物理核心上运行.
一个理智的操作系统会尝试在自己的内核上安排计算密集型任务,但是当您启动上下文切换时会出现问题.现代操作系统仍然倾向于在核心上安排工作,而这些核心在调度时没有工作,但这可能导致并行应用程序中的进程相当自由地从核心交换到核心.对于并行应用程序,您不希望这样,因为您丢失了该进程可能在其核心的高速缓存中使用的数据.人们使用处理器亲和力来控制它,但在Linux上,sched_affinity()的语义在发行版/内核/供应商等之间可能会有很大差异.
如果您使用的是Linux,则可以通过Portable Linux Processor Affinity Library(PLPA)轻松控制处理器关联.这就是OpenMPI内部使用的内容,以确保流程在多核和多串口系统中安排到自己的内核; 他们只是将模块拆分为独立项目.OpenMPI在洛斯阿拉莫斯的许多其他地方使用,所以这是经过良好测试的代码.我不确定Windows下的等价物是什么.
我一直在寻找关于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().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 ls = new List (); ls.Add(10); for (int j = 1; j != 30000; ++j) { ls.Add((int)ls.Average()); } } }