为什么英特尔在其处理器中隐藏了内部RISC核心?

101
从奔腾Pro(P6微体系结构)开始,英特尔重新设计了它的微处理器,并在旧的CISC指令下使用内部RISC核心。自从奔腾Pro以来,所有CISC指令都被分成较小的部分(uops),然后由RISC核心执行。
一开始我就清楚地看出,英特尔决定隐藏新的内部架构,并强制程序员使用“CISC外壳”。由于这个决定,英特尔可以完全重新设计微处理器的架构而不会破坏兼容性,这是合理的。
然而,我不明白一件事情,为什么英特尔仍然保持内部的RISC指令集多年不公开?为什么他们不让程序员像使用旧的x86 CISC指令集一样使用RISC指令?
如果英特尔长期保持向后兼容性(我们仍然拥有虚拟8086模式和64位模式),为什么他们不允许我们编译程序以便绕过CISC指令并直接使用RISC核心呢?这将自然地开辟一条逐渐放弃x86指令集的道路,该指令集现在已经过时了(这是英特尔决定在内部使用RISC核心的主要原因,对吗?)。
看着新的英特尔“Core i”系列,我发现他们只是扩展了CISC指令集,添加了AVX、SSE4和其他指令。

4
请注意,某些x86 CPU会暴露出其内部的RISC指令集。 - phuclv
7个回答

100

不,x86指令集肯定没有被弃用。它仍然像以往一样受欢迎。因为RISC风格的微指令可以更高效地处理,所以英特尔在内部使用这些微指令集。

x86 CPU的工作原理是具有前端重型解码器,它接受x86指令并将其转换为最优化的内部格式,供后端处理。

至于将此格式暴露给“外部”程序,有两个要点:

  • 它不是稳定的格式。英特尔可以在CPU模型之间更改它以最佳适应特定架构。这使他们能够最大程度地提高效率,如果他们不得不就一个固定的、稳定的指令格式来进行内部和外部使用,就会失去这种优势。
  • 这样做没有任何好处。随着如今庞大而复杂的CPU,解码器只是CPU的相对较小部分。必须解码x86指令,这使得它更加复杂,但CPU的其余部分不受影响,因此总体上没有多少好处,特别是因为x86前端仍然需要存在,以执行“遗留”代码。因此,您甚至无法节省当前用于x86前端的晶体管。

这不是一个完美的解决方案,但成本相对较小,比设计支持两个完全不同的指令集要好得多。 (在那种情况下,他们可能最终会发明一个第三组微操作用于内部使用,只是因为这些可以根据CPU的内部架构自由调整)


2
好的观点。RISC是一种良好的核心架构,其中“好”意味着运行快速且可能正确实现,而x86 ISA具有CISC架构历史,仅仅是一个指令集布局,具有悠久的历史和大量可用的二进制软件,同时对于存储和处理也非常高效。它不是一个CISC外壳,而是业界事实上的标准ISA。 - Warren P
4
@Warren:关于最后一部分,我并不这样认为。一个精心设计的 CISC 指令集在存储方面更有效,是的,但从我看到的少数测试中,“平均” x86 指令大约有 4.3 个字节宽,这比在 RISC 架构中通常的要更多。x86 失去了很多存储效率,因为它在多年的扩展和设计过程中变得非常杂乱无序。但正如你所说,它的主要优势在于历史和巨大的现有二进制代码量。 - jalf
2
我并没有说它是“设计良好的CISC”,只是“历史悠久而已”。好的部分是RISC芯片设计的部分。 - Warren P
4
@jalf - 从检查实际二进制文件来看,x86指令的平均大小约为每个3个字节。当然也有更长的指令,但较小的指令往往在实际使用中占主导地位。 - srking
3
平均指令长度不是代码密度的好衡量标准:在典型代码中,x86指令最常见的类型是加载和存储(只是将数据移动到可以处理的位置,然后返回到内存,RISC处理器和约1/2的CISC具有大量寄存器,因此不需要这样做。此外,一个指令可以完成多少操作(ARM指令可以完成约3个操作)。 - ctrl-alt-delor
显示剩余10条评论

23

真正的答案很简单。

RISC处理器实施背后的主要原因是减少复杂性并提高速度。RISC的缺点是指令密度较低,这意味着用RISC格式表示的相同代码需要比等效的CISC代码更多的指令。

如果您的CPU与内存以相同的速度运行,或者至少它们都以相当类似的速度运行,那么这种副作用就不会产生太大影响。

目前,与CPU速度相比,内存速度显示出大的时钟差异。当前的CPU有时比主内存快五倍或更多。

技术状态支持更密集的代码,即CISC提供的内容。

您可以争论缓存可以加快RISC CPU的速度。但同样也适用于CISC处理器。

使用CISC和缓存比使用RISC和缓存可以获得更大的速度提升,因为相同大小的缓存对CISC提供的高密度代码具有更大的影响。

另一个副作用是RISC对编译器实现更加困难。优化CISC处理器的编译器更容易等等。

英特尔知道自己在做什么。

这是如此真实,以至于ARM有一个更高的代码密度模式称为Thumb。


3
同时,一个内部的RISC核心可以减少CISC CPU上的晶体管数量。你不需要为每个CISC指令硬连线,而是可以使用微码来执行它们。这样可以重用RISC微码指令来执行不同的CISC指令,从而使用更少的芯片面积。 - Sil
英特尔指出,通常一条指令会被解码成多个μOps。但有很多情况下,连续的多条指令会被融合成一个单独的μOp。他们举了一个例子,即比较后跳转,这将被融合成一个单独的μOp。 - Ian Boyd

17
如果英特尔保持如此长时间的向后兼容性(我们仍然在64位模式旁边有虚拟8086模式),为什么不允许我们编译程序,使它们绕过CISC指令直接使用RISC内核?这将自然地逐渐放弃现今已经过时的x86指令集(这是英特尔决定在内部使用RISC内核的主要原因,对吗?)。
你需要考虑到商业角度。英特尔实际上曾试图远离x86,但这正是公司制造金蛋的鹅。XScale和Itanium从未接近他们核心的x86业务所取得的成功水平。
基本上,你正在要求英特尔为了开发人员的热情而自掘坟墓。削弱x86并不符合他们的利益。任何使更多的开发人员不必选择目标x86的事情都会削弱x86。这反过来又削弱了他们。

6
是的,当英特尔尝试做这件事(Itanium芯片)时,市场只是无动于衷地回应了。 - Warren P
2
应该注意到,Itanium 失败的原因有很多,不仅仅因为它是一种新的架构。例如,将 CPU 调度卸载给一个从未实现其目标的编译器。如果 Itanium 比 x86 CPU 快 10 倍或 100 倍,那么它会像热馅饼一样畅销。但是它不够快。 - Katastic Voyage

12
Via C3处理器确实允许这样的操作,在启用MSR并执行未记录的0F 3F指令以激活https://en.wikipedia.org/wiki/Alternate_Instruction_Set后,不强制执行通常的特权(环0)与非特权(环3)保护。 (不幸的是,Via Samuel II默认启用了允许此功能的MSR设置。他们也没有记录它,因此操作系统不知道他们应该关闭该功能。其他Via CPU默认禁用。)
请参阅Christopher Domas在DEF CON 26的演讲:
GOD MODE UNLOCKED Hardware Backdoors in redacted x86
他还开发了一个AIS(Alternate Instruction Set)汇编程序:
https://github.com/xoreaxeaxeax/rosenbridge,以及用于激活它(或关闭漏洞!)的工具。
在运行0F 3F(跳转到EAX)后,AIS指令在4字节的RISC指令前面编码有3字节前缀。(与现有的x86指令编码不同,例如它接管了LEA和Bound,但您可以通过混合使用RISC和x86指令来做其他事情。)
AIS(备用指令集)使用类似RISC的固定宽度32位指令;因此,我们已经知道并非所有可能的uop都可以编码为RISC指令。计算机将x86指令解码为单个uop,例如6字节的add eax,0x12345678(带有32位立即数)。但是,32位指令字没有足够的空间容纳32位常量操作码和目标寄存器。因此,它是一种备用的类RISC ISA,仅限于后端可以执行的子集以及其RISC解码器可以从32位指令中解码的内容。

(相关: 处理器是否可以支持多个ISA?(例如:ARM + x86)讨论了这样做的一些挑战,不仅仅是一个噱头,比如具有实际性能期望的完整ARM模式,以及ARM所需的所有寻址模式和指令。)


uops不如实际的ARM或PowerPC好

@jalf的答案涵盖了大部分原因,但有一个有趣的细节没有提到:内部类似RISC的核心并非设计用于运行像ARM/PPC/MIPS这样的指令集。x86税不仅体现在耗电量高的解码器上,而且在某种程度上贯穿整个核心。也就是说,不仅仅是x86指令编码;每个具有奇怪语义的指令都是如此。

(除非那些笨拙的语义使用多个uops处理,这样你就可以只使用一个有用的uop。例如,对于带有原始uops的shl reg,cl,你可以省略在移位计数为0时不修改FLAGS的不方便要求,这就是为什么shl reg,cl在Intel SnB系列上是3个uop,因此使用原始uops将是很好的选择。如果没有原始uops,你需要BMI2 shlx来进行单个uop移位(完全不影响FLAGS)。)

假设英特尔创建了一种操作模式,其中指令流不是x86,而是直接映射到uops的指令。同时,每个CPU型号都有自己的ISA用于此模式,因此它们仍然可以随意更改内部结构,并使用最少量的晶体管公开它们以进行此备用格式的指令解码。
可以推测,您仍然只有相同数量的寄存器,映射到x86体系结构状态,因此x86操作系统可以在上下文切换时保存/恢复它,而无需使用CPU特定的指令集。但是,如果我们放弃这个实际限制,是的,我们可以有更多的寄存器,因为我们可以使用保留给微码的隐藏临时寄存器。
如果我们只是有备用解码器而没有对后续流水线阶段(执行单元)进行任何更改,这个 ISA 仍然会有许多 x86 的特点。它不会是一个非常好的 RISC 架构。没有单个指令会非常复杂,但一些其他的 x86 疯狂也仍然存在。
例如:像 cvtsi2sd xmm0,eax 这样的 int->FP 转换合并到 XMM 寄存器的低元素中,因此对旧寄存器值有一个(错误的)依赖性。即使 AVX 版本只需为要合并的寄存器取一个单独的参数,而不是零扩展到 XMM/YMM 寄存器。这显然不是通常想要的,所以 GCC 通常会做一个额外的 pxor xmm0,xmm0 来打破先前使用 XMM0 的依赖关系。同样,sqrtss xmm1,xmm2 合并到 xmm1。

再一次强调,没有人想要这个(或者说在极少数情况下他们可能会模拟它),但SSE1是在奔腾III时代设计的,当时英特尔的CPU将XMM寄存器处理为两个64位半部分。将其零扩展到完整的XMM寄存器将使每个标量浮点指令增加一个额外的uop,但打包的浮点SIMD指令已经是2个uop。但这是非常短视的,不久之后P4就有了全宽度的XMM寄存器。(尽管在放弃P4后返回P6核心的Pentium-M和Core(不是Core2)仍然具有半宽度的XMM硬件)。因此,英特尔在P-III上的短期收益对于编译器和未来必须运行具有额外指令或可能存在假依赖性的代码的CPU来说是长期的痛苦。

如果要为RISC ISA制作全新的解码器,可以让它挑选x86指令的部分作为RISC指令来暴露,这可以在某种程度上减轻核心的x86专业化。


指令编码可能不是固定大小的,因为单个微操作能够包含大量数据,这比所有指令都具有相同大小更加合理。单个微操作可以添加32位立即数和一个使用两个寄存器和32位位移地址模式的内存操作数。(在Sandy Bridge及以后版本中,只有单寄存器寻址模式可以与ALU操作混合微操作)。
微操作非常大,并且与固定宽度的ARM指令非常不相似。固定宽度的32位指令集一次仅能加载16位立即数,因此加载32位地址需要一个load-immediate低半部分/ loadhigh-immediate对。x86无需执行该操作,这有助于避免由于只有15个通用寄存器而限制常数保留在寄存器中的能力而变得糟糕。(15个通用寄存器比7个要好得多,但再次增加到31个帮助较少,我认为一些仿真发现。RSP通常不是通用寄存器,因此更像是15个通用寄存器和一个堆栈。)

TL;DR摘要:

总之,这个答案归结为“x86指令集可能是编程必须快速运行x86指令的CPU的最佳方式”,但希望能阐明原因。


前端和后端中的内部uop格式

参见微融合和寻址模式,其中介绍了在英特尔CPU上前端和后端uop格式所代表的差异之一。

注1:微码使用一些“隐藏”的寄存器作为临时寄存器。这些寄存器与x86架构寄存器一样被重命名,因此多uop指令可以乱序执行。

例如,在英特尔CPU上,xchg eax,ecx的解码为3个uop(为什么?),我们最好的猜测是这些是类似于MOV的uop,执行tmp = eax; ecx = eax; eax = tmp;。按照这个顺序,因为我测量了dst->src方向的延迟约为1个周期,而另一种方式则为2个周期。而且,这些移动uop不像常规的mov指令;它们似乎不适合零延迟mov消除的候选。

另请参见http://blog.stuffedcow.net/2013/05/measuring-rob-capacity/,其中提到尝试实验性地测量PRF大小,并需要考虑用于保存体系结构状态(包括隐藏寄存器)的物理寄存器。

在前端解码器之后,但在将寄存器重命名到物理寄存器文件的发出/重命名阶段之前,内部uop格式使用类似于x86寄存器编号的寄存器编号,但有足够的空间来寻址这些隐藏寄存器。

uop格式在乱序核心(ROB和RS)内部略有不同,也称为后端(发出/重命名阶段之后)。Haswell中,int / FP物理寄存器文件每个都有168个条目,因此uop中的每个寄存器字段需要足够宽以寻址那么多个寄存器。

由于重命名器在硬件中存在,我们最好使用它,而不是直接将静态调度的指令馈送到后端。因此,我们可以使用一组寄存器,其大小与x86体系结构寄存器+微代码临时寄存器相同,不会更大。

后端设计用于与避免WAW / WAR风险的前端重命名器一起工作,因此即使我们想要,也不能像顺序CPU那样使用它。它没有互锁来检测这些依赖关系; 这由发出/重命名处理。

如果我们能够在不经过发射/重命名阶段(现代英特尔流水线中最狭窄的点,例如Skylake上的4个宽度与后端的4个ALU + 2个加载 + 1个存储端口相比)的情况下将uops输入到后端,那会很不错。但是,如果这样做,我认为您无法静态调度代码以避免寄存器重用并覆盖仍需要的结果,如果缓存未命中导致加载停顿了很长时间。

因此,我们基本上需要将uops馈送到发射/重命名阶段,可能只绕过解码,而不是uop缓存或IDQ。然后,我们可以获得正常的乱序执行和合理的危险检测。寄存器分配表仅设计将16个加上一些整数寄存器重命名为168个条目的整数PRF。我们不能期望硬件将更大的逻辑寄存器集重命名为相同数量的物理寄存器;这需要一个更大的RAT。


8
答案很简单。英特尔不是为开发人员开发CPU!他们正在为做出购买决策的人们开发CPU,这是世界上每个公司所做的! 英特尔早就承诺,在合理范围内,他们的CPU将保持向后兼容。人们想知道,当他们购买一台新的基于英特尔的计算机时,所有当前软件都将与其在旧计算机上运行完全相同。(虽然希望更快!)此外,英特尔确切地知道这个承诺有多么重要,因为他们曾试图走另一条路。你认识多少人拥有Itanium CPU?!你可能不喜欢它,但那个决定,留在x86架构,使英特尔成为世界上最知名的商业名称之一!

2
我不同意暗示英特尔处理器不友好开发者的说法。作为一个多年编写PowerPC和x86代码的程序员,我认为CISC更加友好。 (我现在在英特尔工作,但在被雇用之前我已经对这个问题做出了决定。) - Jeff Hammond
1
@Jeff,这绝不是我的本意!问题是,为什么英特尔没有开放RISC指令集,以便开发人员可以使用它。我并没有说x86不友好于开发人员。我说的是,像这样的决定并不是考虑到开发人员的利益而做出的,而是纯粹的商业决策。 - geo
英特尔曾两次尝试走不同的道路。在Itanium之前,iAPX 432旨在取代x86,但失败了。 - dan04

1

英特尔一直是领导者,直到最近。他们没有改变架构的理由,因为每年通过更好的内部优化进行的迭代式更改使他们走在了前面。此外,AMD——在台式机和服务器CPU领域唯一的真正竞争对手——也使用x86。因此,两家公司中的任何一家都必须在每年优化x86代码方面击败另一家。

为新架构和指令集创建相应的技术是企业的巨大风险,因为这将使他们放弃在x86优化比赛中的立足点,投资人才来创建需要得到Microsoft和/或Linux的广泛支持的新架构。与微软合作,在Windows操作系统中实现二进制翻译(必需品)除非两家制造商同意签署协议并共同努力创建一种标准架构,以便微软可以将其翻译层翻译成该架构。

苹果最近发布了他们的新M1芯片,实际上只是ARM,但这些芯片本质上是RISC,你在汇编中编写的内容就是运行在CPU上的内容。这需要苹果和制造商之间的密切合作,这是他们公司一直做得相当不错的事情(这有其利弊)。他们能够通过对软件和硬件的严格控制来创建所需的确切翻译层,以便让特定的硬件运行。
我的预测是,AMD和英特尔将在不久的将来推出仅支持RISC的CPU,因为毫无疑问,苹果将继续改进"M"系列芯片,在编译器/软件方面提前进行更好的优化,使他们的芯片在需要时具有所需的精确代码。这种方法显然更好,但正如我之前所说:英特尔和AMD被困在步伐一致中,无法承担这一举动。现在他们的手被迫了。
关于为什么他们隐藏内部RISC架构的主要问题?我认为这个问题有点“偏离”。不是说他们故意从你那里“隐藏”它...这意味着有意让你远离它。你不能访问的真正原因是,让你在同一个核心上使用两种架构需要更多的工作。你需要两条管道,代码可以作为数据输入。你如何同步时钟?它们能互相操作吗?如果它们被隔离开来,你会失去一个x86核心,而得到一个RISC核心吗?或者同一个核心可以同时运行两个架构?潜在的安全漏洞怎么办...我们能否让RISC代码以一种干扰内部优化器的方式干扰x86代码?我可以继续下去,但我想你明白我的意思:让这个东西支持两种架构太难了。
那只留下了一个选择:我们必须选择要支持的架构。正如我之前解释的那样,在几段话中,他们不能仅提供RISC处理器的原因有很多。所以我们被我们的技术霸主赋予了x86。

你同步时钟了吗?如果两个前端是同一个物理核心的一部分,那么一开始就没有单独的时钟。我们谈论的是x86核心能够解码备用指令集,而不是在同一多核芯片上完全分离的物理核心。否则,是的,你的观点是合理的。 - Peter Cordes

-4
为什么他们不允许我们编译程序,使它们可以绕过CISC指令直接使用RISC内核?
除了之前的答案外,另一个原因是市场分割。一些指令被认为应该在微码中实现,而不是在硬件中实现,因此允许任何人执行任意微操作可能会削弱销售“新”的更高性能CISC指令的新CPU。

1
我认为这没有意义。如果我们只是在x86前端添加RISC解码器,那么RISC可以使用微码。 - Peter Cordes
4
还是错的。AES新指令(以及即将推出的SHA指令),以及其他类似PCLMULQDQ的东西都有专用硬件支持。在Haswell上,AESENC解码为单个uop(http://agner.org/optimize/),因此绝对没有进行微码化处理。(解码器只需要激活微码ROM序列器[对于解码为超过4个uop的指令](https://dev59.com/il8d5IYBdhLWcg3wXRMX))。 - Peter Cordes
1
你说得对,一些新指令确实只是以一种x86指令不可用的方式使用现有功能。一个很好的例子就是BMI2 [SHLX](http://www.felixcloutier.com/x86/SARX:SHLX:SHRX.html),它允许您进行可变计数移位而无需将计数放入CL,并且无需承担处理糟糕的x86标志语义所需的额外uops(如果移位计数为零,则标志未修改,因此'SHL r / m32,cl'在Skylake上解码为3个uop。但根据Agner Fog的测试,在Core2 / Nehalem上只有1个uop。) - Peter Cordes
感谢您的评论。 - KOLANICH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接