单线程程序是否在 CPU 中并行执行?

Do single threaded programs execute in parallel in a CPU?

在测量英特尔第 4 代 i5 的 CPI(每条指令的周期数)时,我们发现 CPI < 1。

有同学提出是代码并行执行的问题,但是是C中的单线程代码,老师说现在的处理器都是超标量的。

编译是用 gcc -m32 完成的。

假设编译器没有通过并行化代码来施展魔法。

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

假设我们有这两条指令:

(1) 添加 %eax, (%eax) (1) 添加 %ebx, (%ebx)

Core-0 运行s (1) 和 Core-1 运行s (2)

了解一些内容很重要 distinction between parallelization and concurrency。虽然这些术语经常互换使用,但它们实际上是不同的。

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

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

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

是的,CPUs 发现单个线程内的指令级并行性到每个周期 运行 多于 1 条指令。具体示例请参见 Why does re-initializing a register inside an unrolled ADD loop make it run faster even with more instructions inside the loop?

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

现代多核系统同时利用了两者,但您可以两者兼而有之。

例如 Sun 的 Niagara (UltraSPARC T1) is designed from the ground up to exploit thread-level parallelism (and memory-level parallelism) but not try to run any single thread fast, like some kind of server workloads. It has 8 physical single-issue in-order cores 和 4 路 SMT(每个物理内核 4 个逻辑内核)而不是 OoO exec 来隐藏延迟(例如缓存未命中)。单线程性能很糟糕,但最大吞吐量 运行ning 32 线程在 2005 年的功率预算和 t运行sistor 数量是不错的。

早期的 x86,如 Pentium III,是超标量单核。只有 multi-socket 系统是 SMP。但是这样的 CPU 能够并且确实实现了 CPI < 1。

您的第 4 代 i5 CPU 是 Haswell。 参见David Kanter's deep dive on Haswell's microarchitecture,包括每个核心内部各个级的宽度框图。

Do processors run in multiple core single-threaded programs?

NO,单核本身就是超标量的,例如在 Haswell 或 Zen 中有 4 个整数 ALU 执行单元。 (在 Intel CPUs 上,3 个 SIMD ALU 执行单元与标量/通用整数 ALU 在同一执行 ports 上。)和前端宽足以匹配。

一般来说,超标量 CPU 能够 运行 每个时钟 每个内核 .

至少执行 2 条指令

你问题中的这个错误猜测是 programmers.SE 上 How does a single thread run on multiple cores? 的重复。每个核心都有一个宽前端和多个后端执行单元。

直到我们减少 returns 使单核更宽,我们一直这样做 而不是 构建多核 CPU;时间片/先发制人的多任务处理通常就足够了。几乎所有情况下,一个更快的核心都优于 1/N 速度的 N 个核心。但是现在这不是权衡;它是 1/sqrt(N) 速度或类似速度的 N 个内核。


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

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

Ice Lake 也可以在同一个周期内执行两个存储,但在此之前,现代 x86 CPUs 每个时钟只执行 1 个存储。 (例如,从 Haswell 到 Coffee Lake 的英特尔每个时钟周期可以执行 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;

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


我选择了不同类型指令的示例,因为这样可以很容易地理解每条指令需要不同的资源。但是,现代 CPUs 通常包含多个关键资源副本。例如,它们包含大量能够处理整数 addition/subtraction 的 ALU,并使用复杂的寄存器重命名方案来使用比汇编程序员可见的寄存器更多的物理寄存器。这允许它们并行执行两条独立的指令,即使它们属于同一类型(例如整数加法)并且形式上对相同的寄存器进行操作。

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

超标量处理器能够同时处理 fetch/decode/execute 多条指令。这是通过提供足够的硬件资源来处理多个指令来实现的。例如:执行阶段会有多个ALU等