单个线程如何在多个内核上运行?


61

我试图从高层次上理解单个线程如何跨多个内核运行。以下是我的最佳理解。我不认为这是正确的。

根据我对“ 超线程”的阅读,看来OS会组织所有线程的指令,使它们不会彼此等待。然后,CPU的前端通过向每个内核分配一个线程来进一步组织这些指令,并在任何打开周期之间分配来自每个线程的独立指令。

因此,如果只有一个线程,那么操作系统将不会进行任何优化。但是,CPU的前端将在每个内核之间分配独立的指令集。

根据https://stackoverflow.com/a/15936270,特定的编程语言可能会创建更多或更少的线程,但是在确定如何处理这些线程时这是无关紧要的。OS和CPU处理此问题,因此无论使用哪种编程语言,都会发生这种情况。

在此处输入图片说明

只是为了澄清一下,我要问的是在多个内核上运行一个线程,而不是在一个内核上运行多个线程。

我的摘要有什么问题?线程的指令在哪里以及如何划分到多个内核之间?编程语言重要吗?我知道这是一个广泛的主题;我希望对此有一个高层次的理解。


6
单个软件线程的一组指令可以在多个内核上运行,但不能一次运行。
Kroltan

1
您正在混合使用软件线程(涉及OS调度程序)和硬件线程或HyperThreading(使一个内核表现为两个内核的CPU功能)。
ugoren

2
我有20位司机和4辆卡车。一位驾驶员如何用两辆卡车运送包裹?一辆卡车可能有多个驾驶员?这两个问题的答案是相同的。轮流。
埃里克·利珀特

Answers:


84

操作系统为符合条件的线程提供CPU 时间片

如果只有一个内核,则操作系统会调度最合格的线程在该内核上运行一个时间片。在一个时间片完成之后,或者当正在运行的线程阻塞在IO上,或者当处理器被外部事件中断时,操作系统会重新评估下一个要运行的线程(它可以再次选择相同的线程,也可以选择另一个线程)。

运行的资格包括公平性,优先级和准备状态的变化,并且通过这种方法,各种线程获得了时间片,其中一些比其他线程更多。

如果有多个核心N,则操作系统计划最合格的N个线程在核心上运行。

处理器亲和力是效率方面的考虑。每次CPU运行与以前不同的线程时,它趋于降低速度,因为它的缓存对于上一个线程而言是暖和的,对新线程而言则是冷的。因此,在多个时间片上在相同处理器上运行相同线程是效率优势。

但是,操作系统可以在不同的CPU上自由地提供一个线程的时间片,并且可以在不同时间片上的所有CPU中进行旋转。但是,正如@ gnasher729所说,它不能同时在多个CPU上运行一个线程。

超线程是硬件中的一种方法,通过该方法,单个增强型 CPU内核可以支持同时执行两个或更多不同线程。(这样的CPU可以以比其他全核更低的成本在硅芯片领域中提供更多的线程。)这个增强的CPU内核需要支持其他线程的其他状态,例如CPU寄存器值,并且还具有协调状态和行为,无需共享线程即可在该CPU中共享功能单元。

从硬件的角度来看,超线程虽然在技术上具有挑战性,但从程序员的角度来看,其执行模型只是附加CPU内核的模型,而不是更复杂的模型。因此,尽管有一些新的处理器亲缘性问题,但由于几个超线程线程共享一个CPU内核的缓存体系结构,因此操作系统还会看到其他CPU内核问题。


我们可能天真地认为,在超线程内核上运行的两个线程各自的运行速度是它们在拥有自己完整内核时的运行速度的一半。但这不是必须的,因为单个线程的执行充满了松弛周期,并且其他超线程线程可以使用其中的一定数量。此外,即使在非松弛周期中,一个线程可能使用的功能单元与另一个线程不同,因此可以同时执行。用于超线程的增强型CPU可能有一些特定的频繁使用的功能单元专门支持该功能单元。


3
“因此,在多个时间片上在同一处理器上运行同一线程是一种效率优势。” 不必一定是连续的时间片吗?否则,缓存将被其他线程擦除,不是吗?+1为一个很好的解释。
jpmc26

2
@Luaan:HT通常很好,但是情况并不像您描述的那么简单。线程之间均等地共享前端问题带宽(在Intel上,每个时钟4 uops,在Ryzen上为6 uops)(除非其中一个停顿)。如果这是瓶颈,那么就像我说的那样,HT根本无济于事。如果负载,ALU和存储混合在一起,Skylake经常会在一个经过良好调整的循环中接近它,这是很常见的……晶体管很便宜(而且不能一次全部切换,否则CPU会熔化),因此,现代x86 CPU的执行端口数量超过了前端所能提供的数量(复制了许多执行单元...
Peter Cordes

2
...在多个端口上)...这似乎是一种浪费,但是循环通常只会一次使用一种ALU执行单元,因此,所有内容都重复意味着运行任何类型的代码,都会有多个其说明的端口。因此,您引用受益于HT的原因并不常见,因为大多数代码都有一些负载和/或存储占用了前端带宽,而剩下的通常不足以使执行单元饱和。
彼得·科德斯

2
@Luaan:另外,在Intel CPU中,整数和FP /向量执行单元共享相同的执行端口。例如,FP FMA / mul / add单元位于端口0/1上。但是整数乘数也位于port1上,并且简单的整数ops可以在4个执行端口中的任何一个上运行(我的回答中的图表)。第二个线程使用了问题带宽,即使它们不竞争执行单元,它们也会减慢它们的速度,但是如果它们在缓存方面的竞争不是很糟,通常会获得净吞吐量。甚至x264 / x265(视频编码器)之类的经过优化的高通量代码也可以从HT的Skylake上受益约15%。
彼得·科德斯

3
@luaan除了Peter所说的以外,您声称“那是HT背后的原始原因”是不正确的。HT背后的最初原因是NetBurst微体系结构将管道加长到一个极端的程度(以提高时钟速度为目的),以至分支错误预测和其他管道气泡绝对会破坏性能。HT是Intel解决方案之一,该解决方案可最大程度地减少由于管道中的气泡而使这种昂贵的大型芯片的执行单元处于空闲状态的时间:其他线程的代码可以插入并在这些孔中运行。
科迪·格雷

24

没有一个线程可以同时在多个内核上运行。

但是,这并不意味着不能并行执行来自一个线程的指令。有称为指令流水线无序执行的机制可以允许它。每个内核都有大量的冗余资源,这些资源不会被简单的指令利用,因此,多个这样的指令可以一起运行(只要下一个指令不取决于先前的结果)。但是,这仍然在单个内核内发生。

超线程是该思想的一种极端变体,其中一个内核不仅可以并行执行来自一个线程的指令,而且可以混合来自两个不同线程的指令以进一步优化资源使用。

相关的Wikipedia条目:指令流水线乱序执行


3
它们不能同时运行,但可以并行运行?这些不是一回事吗?
Evorlor '17

10
@Evorlor这里的关键是核心和执行单元之间的区别。单个线程只能在一个内核上运行,但是处理器可以使用动态分析来确定内核正在执行的哪些指令彼此不依赖,并同时在不同的执行单元上执行这些指令。一个内核可以具有多个执行单元。
user1937198

3
@Evorlor:乱序的CPU可以在单个线程的指令流中找到并利用指令级并行性。例如,更新循环计数器的指令通常与循环执行的其他一些工作无关。或者在一个a[i] = b[i] + c[i]循环中,每个迭代都是独立的,因此可以同时运行来自不同迭代的加载,添加和存储。它必须保留这样一种错觉,即指令以程序顺序执行,但是例如,在高速缓存中未命中的存储不会延迟线程(直到它耗尽了存储缓冲区中的空间)。
彼得·科德斯

3
@ user1937198:短语“动态分析”将更适合JIT编译器。乱序的CPU并没有真正分析。它更像是一种贪婪算法,可以运行已解码和发出的所有指令并准备好其输入。(无序的重新排序窗口受到一些微体系结构资源的限制,例如Intel Sandybridge的ReOrder Buffer大小为168 uop。另请参见通过实验测量ROB大小)。所有这些都由硬件状态机实现,每个时钟处理4 uops。
彼得·科德斯

3
@Luaan是的,这是一个有趣的想法,但是AOT编译器仍然不够聪明,无法充分利用它。而且,Linus Torvalds(和其他人)认为,暴露出管道的许多内部结构是未来设计的一大制约因素。例如,如果不更改ISA,就无法真正增加管道宽度。或者,您构建了一个以常规方式跟踪依赖关系的CPU,并可能并行发出两个VLIW组,但是您失去了EPIC的CPU复杂性优势,但仍然有缺点(当编译器无法填充时丢失了发布带宽)一个字)。
彼得·科德斯

22

摘要:在单线程程序中查找和利用(指令级)并行性完全是在硬件上完成的,这取决于运行它的CPU内核。 而且仅在数百条指令的窗口上显示,而不是大规模重新排序。

单线程程序没有从多核CPU中获得任何好处,除了其他事情可以在其他内核上运行,而不是花时间去做单线程任务。


OS会组织所有线程的指令,以免它们相互等待。

操作系统不会在线程的指令流中查找。它仅将线程调度到内核。

实际上,每个内核在需要弄清楚下一步要做什么时,都会运行OS的调度程序功能。调度是一种分布式算法。为了更好地理解多核计算机,请将每个核都视为单独运行内核。就像多线程程序一样,编写内核是为了使一个内核上的代码可以与其他内核上的代码安全地交互以更新共享的数据结构(例如准备运行的线程列表)。

无论如何,OS都参与了帮助多线程进程利用线程级并行性的过程,必须通过手动编写多线程程序来显式地公开线程级并行性。(或通过带有OpenMP的自动并行化编译器等)。

然后,CPU的前端通过向每个内核分配一个线程来进一步组织这些指令,并在任何打开周期之间分配来自每个线程的独立指令。

如果未暂停,则CPU内核仅运行一条指令流(直到下一个中​​断(例如定时器中断)时才进入睡眠状态)。通常这是一个线程,但也可能是内核中断处理程序,或者如果内核决定执行其他操作,而不只是在处理和中断或系统调用后返回上一个线程,则可能是其他内核代码。

在HyperThreading或其他SMT设计中,物理CPU内核的作用类似于多个“逻辑”内核。从操作系统的角度来看,具有超线程的四核(4c8t)CPU和普通的八核计算机(8c8t)之间的唯一区别是,支持HT的OS将尝试调度线程以将物理内核分开,因此它们不会彼此竞争。一个不知道超线程的操作系统只会看到8个内核(除非您在BIOS中禁用了HT,然后它只会检测到4个内核)。


术语“ 前端”是指CPU内核的一部分,用于提取机器代码,解码指令并将它们发布到内核的无序部分中。每个核心都有自己的前端,并且是整个核心的一部分。它获取的指令 CPU当前正在运行的指令。

在内核的乱序部分内,当指令(或uops)的输入操作数准备就绪并且有一个空闲的执行端口时,它们便被调度到执行端口。这不必按程序顺序进行,因此OOO CPU可以通过这种方式在单个线程内利用指令级并行性

如果您在您的想法中将“核心”替换为“执行单元”,您将接近正确。是的,CPU确实将独立的指令/指令并行分配给执行单元。(但是有一个术语混用,因为您实际上是说CPU的指令调度程序(即预留站)选择了可以执行的指令,所以您说的是“前端”。

乱序执行只能在非常本地的级别上找到ILP,最多只能找到数百条指令,而不能在两个独立的循环之间找到它们(除非它们很短)。


例如,此的asm等效项

int i=0,j=0;
do {
    i++;
    j++;
} while(42);

只会在Intel Haswell上增加一个计数器的速度与同一个循环一样快。 i++仅取决于的先前值i,而j++仅取决于的先前值j,因此两个依赖关系链可以并行运行,而不会破坏按程序顺序执行所有操作的幻觉。

在x86上,循环看起来像这样:

top_of_loop:
    inc eax
    inc edx
    jmp .loop

Haswell具有4个整数执行端口,并且所有端口均具有加法器单元,因此,inc如果它们都是独立的,则每个时钟最多可维持4 条指令的吞吐量。(在延迟= 1的情况下,通过保持4 inc条指令处于运行状态,您只需要4个寄存器即可使吞吐量达到最大值。将其与向量FP MUL或FMA进行对比:延迟= 5吞吐量= 0.5需要10个向量累加器才能使10个FMA处于运行状态以最大化吞吐量。每个向量可以为256b,包含8个单精度浮点数)。

分支转移也是一个瓶颈:循环每次迭代至少要占用一个完整的时钟,因为分支转移的吞吐量限制为每个时钟1个。我可以把循环内一个指令,而不会降低性能,除非它也读/写eaxedx在这种情况下将延长这种依赖性链。在循环中再添加2条指令(或一条复杂的多uup指令)会在前端造成瓶颈,因为每个时钟只能向混乱的内核发出4 uu。(请参见此SO Q&A,以获得有关不是4微倍的循环发生的一些详细信息:循环缓冲区和uop缓存使事情变得有趣。)


在更复杂的情况下,发现并行性需要查看更大的指令窗口。(例如,也许有一系列的10条指令相互依赖,然后是一些独立的指令)。

重排序缓冲区容量是限制无序窗口大小的因素之一。在Intel Haswell上,是192微秒。(您甚至可以通过实验测量它,以及寄存器重命名能力(寄存器文件大小)。)如果ARM的低功耗CPU内核执行的全部乱序,它们的ROB大小要小得多。

还要注意,CPU需要流水线处理,并且顺序混乱。因此,它必须在执行指令之前先取指令并进行解码,最好在没有任何取指令周期之后,要有足够的吞吐量来重新填充缓冲区。分支是棘手的,因为如果我们不知道分支的前进方向,我们甚至不知道从哪里获取。这就是为什么分支预测如此重要的原因。(以及现代CPU为什么使用推测执行的原因:他们猜测分支将走哪条路,并开始获取/解码/执行该指令流。当检测到错误预测时,它们会回滚到最后一个已知良好的状态并从那里执行。)

如果您想了解有关CPU内部的更多信息,请在Stackoverflow x86标签Wiki中找到一些链接,包括指向Agner Fog的microarch指南以及David Kanter撰写的有关Intel和AMD CPU图表的详细文章。从他的Intel Haswell微体系结构文章中可以看出,这是Haswell内核的整个流水线(而不是整个芯片)的最终图。

这是单个 CPU内核的框图。四核CPU在芯片上具有4个这样的芯片,每个芯片都有自己的L1 / L2缓存(共享L3缓存,内存控制器和与系统设备的PCIe连接)。

哈斯韦尔全线

我知道这非常复杂。Kanter的文章还显示了部分内容,例如与执行单元或缓存分开谈论前端。


2
“查找和利用单线程程序中的(指令级)并行性完全是在硬件中完成的”。请注意,这仅适用于常规ISA,不适用于由编译器或程序员完全确定ILP或在硬件之间共同确定ILP的VLIW和软件。
哈迪·布雷斯

1
@ user7813604:是的。超线程无法并行化单个线程。相反,它在一个内核上运行多个线程,从而降低了每个线程的性能,但是却提高了整体吞吐量。
彼得·科德斯

1
@ user7813604:ILP的要点是寻找可以并行运行的指令,同时仍保持每条指令按顺序运行的错觉,每条指令在下一条指令开始之前完成。如果延迟时间大于1,则标量流水线CPU有时可能会因依赖关系而停顿。但是对于超标量CPU,这甚至更大。
彼得·科德斯

1
@ user7813604:是的,我的回答确实是以此为例。例如,Haswell可以inc在同一时钟周期内对其4个整数ALU执行单元最多执行4 条指令。
彼得·科德斯

1
@ user7813604:是的,ILP是可以并行执行的数量。实际的CPU通过在单个内核中并行运行它来发现和利用ILP的能力有限,例如,在Intel中最多可达到4宽的超标量。这个答案试图用例子来解释。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.