摘要:在单线程程序中查找和利用(指令级)并行性完全是在硬件上完成的,这取决于运行它的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个。我可以把循环内一个指令,而不会降低性能,除非它也读/写eax
或edx
在这种情况下将延长这种依赖性链。在循环中再添加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的文章还显示了部分内容,例如与执行单元或缓存分开谈论前端。