在CPU中执行并行的单线程程序



在测量英特尔第四代i5的CPI(每条指令的周期)时,我们得到了一个CPI<1.

一些同学认为这是由于代码的并行执行,但这是C中的单线程代码,老师说现在的处理器是超标量的。

编译是用gcc-m32完成的。

假设编译器没有通过并行化代码来实现魔术。

但我仍然心存疑虑。由于现在的处理器对代码做了一些小魔术,比如无序执行和推测执行,我想知道是否:

  • 处理器是否在多核单线程程序中运行

假设我们有这两条指令:

(1) 加法%eax,(%eax)(1) addl%ebx,(%ebx)

核心-0运行(1)和核心-1运行(2)

是的,CPU在单个线程内发现指令级并行性,每个周期运行一条以上的指令。请参阅为什么在展开的ADD循环中重新初始化寄存器会使其运行速度更快,即使循环中有更多指令?作为一个具体的例子。

指令级并行性与线程级并行性无关(您在问题的第二部分中提到了这一点)。当运行单线程工作负载时,只有一个核心处于活动状态。

现代多核系统同时利用这两种技术,但你可以有一个而没有另一个。

例如,Sun的Niagara(UltraSPARC T1)从一开始就被设计为利用线程级并行性(和内存级并行性),但不试图像某些服务器工作负载那样快速运行任何单个线程。它有8个按顺序排列的物理单问题内核,以及4路SMT(每个物理内核4个逻辑内核),而不是OoO exec,以隐藏延迟(例如缓存未命中)。单线程的性能很糟糕,但运行32个线程的最大吞吐量对于2005年来说是不错的,因为它的功率预算和晶体管数量。

早期的x86和奔腾III一样,都是超标量单核。只有多套接字系统才是SMP。但这样的CPU可以并且确实实现了CPI<1.

您的i5第四代CPU是Haswell请参阅David Kanter对Haswell微体系结构的深入研究,其中包括每个核心内各个阶段的宽度框图。

处理器是否在多核单线程程序中运行?

NO,单个内核本身就是超标量的,例如在Haswell或Zen中有4个整数ALU执行单元。(在英特尔CPU上,3个SIMD ALU执行单元与标量/通用整数ALU位于相同的执行端口上。)以及足够宽的前端。

通常,超标量CPU能够在每个时钟每个内核至少运行2条指令。

你的问题中的这个错误猜测是对"单个线程如何在多个核心上运行?"的重复?关于程序员。SE.答案:他们没有;每个核心都有一个宽的前端和后端的多个执行单元。

在我们实现单核更宽的回报递减之前,我们一直在做,而不是构建多核CPU;时间分割/先发制人的多任务处理通常已经足够好了。一个更快的内核几乎比N个1/N速度的内核更好。但如今,这并不是一种权衡;它是1/sqrt(N)速度的N个核心或类似的东西。


  • 加法%eax,(%eax)
  • addl%ebx,(%ebx)

这些内存目标添加指令每个都需要超过1个周期才能完成(并在现代英特尔上解码到每个至少2个uops:加载+添加微融合,并存储(微融合存储地址+存储数据)。如果加载+添加部件在同一物理核心上运行,则它们可以在同一周期中启动。

Ice Lake也可以在同一个周期中执行两个存储,但在此之前,现代x86 CPU每个时钟只执行一个存储。(例如,从Haswell到Coffee Lake的Intel每个时钟周期可以进行2次加载+1次存储。SnB/IvB每个周期可以为2次内存操作生成地址,如果其中一次是存储,则可以维持吞吐量。256位矢量的特殊2+1情况下,在2个数据周期内重复使用相同的地址生成。)

除非EAX和EBX持有相同的指针值,否则这些指令访问不同的内存和不同的寄存器,并且除了执行单元(加载、添加、存储)的资源冲突之外,它们是完全独立的。(寄存器重命名处理FLAGS输出的一次又一次写入的危险)。

是。CPU的很大一部分专门用于所谓的调度,将工作分配给CPU的内部资源。每当这个调度电路能够证明两个指令没有与它们所需的资源(功能单元,如不同的ALU,最重要的是寄存器)冲突时,这些指令可以并行调度。这可能导致CPI低于1。

相互独立的典型指令有控制流(分支)、整数运算和浮点运算。特别是后两者实际上总是独立的,因为它们需要非常不同的ALU,并且对不同类型的数据进行操作。所以,当你的程序做例如

double a = 7.0, factor = 1.1;
for(int i = 42; i--; ) a *= factor;

您可能会发现浮点电路在整数电路递减并检查循环计数器的同时执行乘法,而控制流电路执行分支到下一个循环迭代。这样的循环有可能在每次迭代中只执行一个循环。。。


我选择了不同类型指令的例子,因为它很容易理解每个指令需要不同的资源。然而,现代CPU通常包含多个密钥资源副本。例如,它们包含大量能够进行整数加法/减法运算的ALU,并使用复杂的寄存器重命名方案来使用比汇编程序员可见的寄存器多得多的物理寄存器。这允许它们并行执行两个独立的指令,即使它们是相同类型(例如,整数加法)并在相同的寄存器上正式操作。

基本上,您可以将CPU前端视为一个即时编译器,它将机器代码转换为内部指令集,包括一个优化器,它试图让尽可能多的CPU资源保持繁忙。

超标量处理器能够同时获取/解码/执行许多指令。这是通过提供足够的硬件资源来处理几个指令来实现的。例如:执行阶段将有多个ALU等

了解并行化和并发之间的一些区别很重要。虽然这些术语经常互换使用,但它们实际上是不同的。

在存在多核处理器和多处理器系统之前,通常通过时间切片来使用并发性,这通过在CPU时钟上的相邻时间片中串行执行代码,使并行处理的外观。最终的结果是,事情将在大致相同的时间完成,并且看起来是同时完成的。值得注意的是,并发仍然被大量使用。

相反,并行化运行多个线程,并允许在各种内核上异步完成工作,这些内核稍后可以进行重组(或多或少),以在UI中提供预期的结果或反馈、游戏中的操作等。

一些现代编译器和CPU/GPU指令集可以并行化代码中没有明确并行的东西。此外,一些基准测试可能高估或低估了给定内核或处理器的线程能力。

最新更新