关于CPU物理学和CPU周期
这是即将出版的《现代64位CPU的高效C++编程》一书第4章第一部分的草稿,作者是Sherry Ignatchenko和Dmytro Ivanchykhin。欢迎您对此进行评论,特别是如果您发现某些事实不一致,我们会很高兴进行修正。追求效率的另一个好处是,这个过程迫使您更深入地理解问题——Alex Stepanov,原STL的作者。现在,让我们开始讨论一些具体细节。让我们看看图1,展示了一块"代表性"的主板,上面有一颗"代表性"的CPU芯片,而这颗芯片又有一个"代表性"的CPU核心。这里,我们故意不提及任何特定的主板或CPU设计,只是说它们在2026年的CPU中“或多或少是常见的”。在深入研究图片的所有令人兴奋的细节之前,让我们假设一件事:电信号物理上必须传输的距离越远,访问速度就越慢。电子信号的特征时间受所谓寄生电容的限制,而寄生电容通常与连接的长度成正比。这在设计良好的电子设备领域几乎是普遍适用的法则;然而,对于较小的距离,它并不是直接与光速的基本限制相关。事实上,光在0.5mm内双向传播只需3e-12秒;而在实际中,即使是注册注册(R-R)操作也需要约3e-10秒,大约多了两个数量级。反而,至少对于CPU核心来说,距离和速度之间的关系是电子信号的特征时间受所谓寄生电容的限制这一观察的表现,而寄生电容通常与连接的长度成正比。核心现在,让我们谈谈图1的细节。通常,当我们写入类似a += b;的操作时,这是一个寄存器到寄存器(R-R)操作,因此数据必须从寄存器传输到算术逻辑单元(ALU,或SIMD),然后再返回。然而,我们所有的64位CPU实际上都是流水线化的,因此当ALU需要这些信息时,它通常已经在ALU入口处,所以在大多数情况下,我们可以避免从寄存器到ALU再返回的延迟惩罚。简单操作如加法/减法和按位运算,通常需要1个CPU周期;乘法大约需要3-6个周期,而除法则需最多20个周期(请注意,与[Fog]相比,64位除法曾经需要超过100个CPU周期,近年来除法时间已有显著改善,见下文《冰冷硬数据》部分的表格)。图1 L1D/L1I L1缓存通常分为L1数据缓存(L1D)和L1指令缓存(L1I)。在图中可以看到的一件有趣的事情是,通常有不止一个ALU(和不止一个SIMD单元);这表明我们的CPU是超标量的,可以同时处理不止一个ALU级别的操作。1这反过来意味着,可能在同一个CPU周期内完成不止一个操作;这可能导致一些反直觉的观察,即在微基准测试中,一些操作可以消耗一些非整数的周期(例如0.75);这并不意味着某个操作实际上消耗了不到一个周期;而是意味着统计上CPU设法组织事务,使得平均每3个周期执行4个操作。更一般地说,现代超标量CPU的所谓每周期退休指令数(RIPC)可以高达10-12,但在实际运行中,达到4的挑战相当大,通常仅限于ALU绑定的算法(与RAM绑定的算法相对)。如果我们需要访问CPU寄存器外的信息,CPU必须访问内存,但由于内存是缓存的,它首先会去最近的L1(实际上是L1D)缓存;从L1D缓存读取通常需要像现在3个CPU周期。然而,请注意,从CPU的角度来看,写入几乎是“瞬时”的;实际上,CPU所需做的只是向L1D发出“只需写入”的指令,无需等待任何事情。如果L1D未命中,转向L2的代价比L1显著更高,通常需要10-15个周期。分支误预测和[[likely]]/[[unlikely]]让我们记住,虽然核心的“指令”部分有很多内容,但在应用层面上,我们不需要对其过于关注;所有内容都近乎完美优化,除非涉及分支,现代编译器确保我们的代码在不延迟的情况下工作——也就是说,当我们的数据到达时,所有指令都已准备好执行,而绝大多数延迟和阻塞与数据有关,而非指令有关。然而,一个显著的例外与分支有关。每当CPU到达某个分支指令(例如JZ / BEQ或JNC / BLO指令)时,
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡