112

我一直在阅读一些 SO 档案,并遇到了针对 x86 架构的声明。

还有更多评论,例如

我尝试搜索但没有找到任何原因。我不觉得 x86 不好,可能是因为这是我熟悉的唯一架构。

有人可以给我考虑 x86 与其他人相比丑/差/劣的理由吗?

4

10 回答 10

100

几个可能的原因:

  1. x86 是一个相对较旧的ISA(毕竟它的祖先是 8086)
  2. x86 已经经历了数次显着发展,但需要硬件来保持与旧二进制文件的向后兼容性。例如,现代 x86 硬件仍然包含对本机运行 16 位代码的支持。此外,存在几种内存寻址模型以允许旧代码在同一处理器上互操作,例如实模式、保护模式、虚拟 8086 模式和 (amd64) 长模式。这可能会让一些人感到困惑。
  3. x86 是 CISC 机器。长期以来,这意味着它比 MIPS 或 ARM 等 RISC 机器慢,因为指令具有数据相互依赖性和标志,使得大多数形式的指令级并行性难以实现。现代实现将 x86 指令翻译成类似于 RISC 的指令,称为“微操作”,以使这些优化在硬件中实现切实可行。
  4. 在某些方面,x86 并不逊色,只是不同而已。例如,输入/输出在绝大多数架构上被处理为内存映射,但在 x86 上却没有。(注意:现代 x86 机器通常具有某种形式的DMA支持,并通过内存映射与其他硬件进行通信;但ISA仍然具有 I/O 指令,例如INand OUT
  5. x86 ISA的架构寄存器非常少,这可能会迫使程序比其他情况更频繁地在内存中往返。执行此操作所需的额外指令占用了可用于有用工作的执行资源,尽管有效的存储转发保持低延迟。将寄存器重命名到大型物理寄存器文件的现代实现可以使许多指令保持运行,但缺乏架构寄存器仍然是 32 位 x86 的一个重大弱点。x86-64 的整数和向量寄存器从 8 个增加到 16 个,这是 64 位代码比 32 位更快(以及更有效的寄存器调用 ABI)的最大因素之一,而不是每个寄存器的宽度增加。将整数寄存器从 16 个进一步增加到 32 个会有所帮助,但作用不大。(不过,AVX512 确实增加到 32 个向量寄存器,因为浮点代码具有更高的延迟并且通常需要更多的常量。)(见评论
  6. x86 汇编代码很复杂,因为 x86 是一个具有许多特性的复杂架构。典型 MIPS 机器的指令列表适合单字母大小的纸张。x86 的等效列表占据了好几页,并且说明只是做更多的事情,因此您通常需要对它们的作用进行更大的解释,而不是列表所能提供的。例如,该MOVSB指令需要一个相对较大的 C 代码块来描述它的作用:

    if (DF==0) 
      *(byte*)DI++ = *(byte*)SI++; 
    else 
      *(byte*)DI-- = *(byte*)SI--;
    

    这是一个执行加载、存储和两个加法或减法(由标志输入控制)的指令,每一条指令都是 RISC 机器上的单独指令。

    虽然 MIPS(和类似的体系结构)的简单性并不一定会使它们变得优越,但对于教授汇编程序类的介绍来说,从更简单的ISA开始是有意义的。一些汇编课程教授 x86 的超简化子集,称为y86,它被简化到对实际使用没有用处(例如,没有移位指令),或者一些只教授基本的 x86 指令。

  7. x86 使用可变长度操作码,这增加了指令解析方面的硬件复杂性。在现代,随着 CPU 越来越受到内存带宽的限制而不是原始计算的限制,这个成本变得越来越小,但是许多“抨击 x86”的文章和态度来自这个成本相对更大的时代。
    2016 年更新:Anandtech 发布了关于 x64 和 AArch64 下的操作码大小的讨论。

编辑:这不应该是x86 的 bash!派对。鉴于问题的措辞,我别无选择,只能进行一些抨击。但是除了(1)之外,所有这些事情都是有充分理由的(见评论)。英特尔设计师并不愚蠢——他们想通过他们的架构实现一些目标,而这些是他们为使这些目标成为现实而必须支付的一些税款。

于 2010-04-21T02:26:26.640 回答
29

在我看来,对 x86 的主要打击是它的 CISC 起源——指令集包含许多隐含的相互依赖关系。这些相互依赖使得在芯片上执行指令重新排序等事情变得困难,因为必须为每条指令保留这些相互依赖的工件和语义。

例如,大多数 x86 整数加减指令会修改标志寄存器。在执行加法或减法之后,下一个操作通常是查看标志寄存器以检查溢出、符号位等。如果之后还有另一个加法,很难判断开始执行第二次加法是否安全在第一次添加的结果已知之前。

在 RISC 体系结构上,add 指令将指定输入操作数和输出寄存器,并且有关操作的所有内容都将仅使用这些寄存器进行。这使得解耦彼此靠近的添加操作变得更加容易,因为没有bloomin'标志寄存器强制所有内容排队并执行单个文件。

DEC Alpha AXP 芯片是 MIPS 风格的 RISC 设计,在可用指令方面非常简陋,但指令集旨在避免指令间隐式寄存器依赖性。没有硬件定义的堆栈寄存器。没有硬件定义的标志寄存器。甚至指令指针也是操作系统定义的——如果你想返回给调用者,你必须弄清楚调用者将如何让你知道要返回的地址。这通常由操作系统调用约定定义。但是,在 x86 上,它是由芯片硬件定义的。

无论如何,经过 3 或 4 代 Alpha AXP 芯片设计,硬件从具有 32 个 int 寄存器和 32 个浮点寄存器的 spartan 指令集的文字实现变成了具有 80 个内部寄存器的大规模无序执行引擎,寄存器重命名,结果转发(前一条指令的结果被转发到取决于值的后一条指令)和各种疯狂和疯狂的性能提升器。加上所有这些花里胡哨的东西,AXP 芯片芯片仍然比当时可比的 Pentium 芯片芯片小得多,而且 AXP 快得多。

在 x86 家族树中,你看不到那种性能提升的爆发,主要是因为 x86 指令集的复杂性使得许多类型的执行优化即使不是不可能也非常昂贵。英特尔的天才之处在于放弃在硬件中实现 x86 指令集——所有现代 x86 芯片实际上都是 RISC 内核,在一定程度上解释 x86 指令,将它们翻译成内部微码,保留原始 x86 的所有语义指令,但允许对微码进行一些 RISC 乱序和其他优化。

我已经编写了很多 x86 汇编程序,并且可以充分体会到它的 CISC 根源的便利性。但是直到我花了一些时间编写 Alpha AXP 汇编程序之后,我才完全意识到 x86 有多么复杂。AXP 的简单性和统一性让我大吃一惊。差异是巨大的,深刻的。

于 2010-04-21T03:04:21.130 回答
21

x86 体系结构可以追溯到 8008 微处理器及其相关产品的设计。这些 CPU 是在内存很慢的时候设计的,如果你可以在 CPU 芯片上完成它,它通常会快很多。但是,CPU 芯片空间也很昂贵。这两个原因是为什么只有少数寄存器往往具有特殊用途,以及具有各种陷阱和限制的复杂指令集。

同一时代的其他处理器(例如 6502 系列)也有类似的限制和怪癖。有趣的是,8008 系列和 6502 系列都旨在用作嵌入式控制器。即使在那个时候,嵌入式控制器也被期望用汇编程序进行编程,并且在许多方面迎合汇编程序员而不是编译器编写者。(看看 VAX 芯片,当你迎合编译器编写时会发生什么。)设计者没想到它们会成为通用计算平台。这就是 POWER 架构的前身之类的东西。当然,家用电脑革命改变了这一点。

于 2010-04-21T03:03:38.907 回答
13

我这里有几个额外的方面:

考虑操作“a=b/c”x86 将其实现为

  mov eax,b
  xor edx,edx
  div dword ptr c
  mov a,eax

作为 div 指令的额外奖励,edx 将包含其余部分。

RISC 处理器需要首先加载 b 和 c 的地址,将 b 和 c 从内存加载到寄存器,进行除法并加载 a 的地址,然后存储结果。dst,src 语法:

  mov r5,addr b
  mov r5,[r5]
  mov r6,addr c
  mov r6,[r6]
  div r7,r5,r6
  mov r5,addr a
  mov [r5],r7

这里通常不会有余数。

如果要通过指针加载任何变量,则两个序列可能会变得更长,尽管这对于 RISC 来说不太可能,因为它可能已经在另一个寄存器中加载了一个或多个指针。x86 的寄存器较少,因此指针位于其中之一的可能性较小。

优点和缺点:

RISC 指令可以与周围的代码混合以改进指令调度,这在 x86 中不太可能,而是在 CPU 本身内部完成这项工作(或多或少取决于序列)。在 32 位架构上,上述 RISC 序列通常为 28 字节长(7 条 32 位/4 字节宽的指令)。这将导致片外存储器在获取指令(七次获取)时工作得更多。更密集的 x86 序列包含更少的指令,尽管它们的宽度各不相同,但您可能也在其中查看平均 4 个字节/指令。即使您有指令高速缓存来加快七次取指的速度,也意味着与 x86 相比,您将在其他地方有 3 次的不足来弥补。

x86 架构具有更少的寄存器来保存/恢复意味着它可能会比 RISC 更快地进行线程切换和处理中断。更多的寄存器来保存和恢复需要更多的临时 RAM 堆栈空间来执行中断和更多的永久堆栈空间来存储线程状态。这些方面应该使 x86 成为运行纯 RTOS 的更好候选者。

就个人而言,我发现编写 RISC 程序集比 x86 更难。我通过用 C 语言编写 RISC 例程、编译和修改生成的代码来解决这个问题。从代码生产的角度来看,这更有效,而从执行的角度来看,这可能效率较低。所有需要跟踪的 32 个寄存器。对于 x86,情况正好相反:6-8 个具有“真实”名称的寄存器使问题更易于管理,并且更加确信生成的代码将按预期工作。

丑陋?那是在旁观者的眼中。我更喜欢“不同”。

于 2010-12-07T10:00:16.863 回答
13

我认为这个问题有一个错误的假设。主要只是痴迷于 RISC 的学者称 x86 丑陋。实际上,x86 ISA 可以在单指令操作中完成,这在 RISC ISA 上需要 5-6 条指令。RISC 粉丝可能会反驳说,现代 x86 CPU 将这些“复杂”指令分解为微操作;然而:

  1. 在许多情况下,这只是部分正确或根本不正确。x86 中最有用的“复杂”指令是诸如mov %eax, 0x1c(%esp,%edi,4)寻址模式之类的东西,这些都没有分解。
  2. 在现代机器上,通常更重要的不是花费的周期数(因为大多数任务都不受 CPU 限制),而是代码的指令缓存影响。5-6 条固定大小(通常为 32 位)指令对缓存的影响远大于一条很少超过 5 字节的复杂指令。

大约 10 到 15 年前,x86 真正吸收了 RISC 的所有优点,而 RISC 的其余特性(实际上是定义的特性- 最小指令集)是有害且不可取的。

除了制造 CPU 的成本和复杂性以及它们的能源需求之外,x86 是最好的 ISA。任何告诉你其他情况的人都会让意识形态或议程妨碍他们的推理。

另一方面,如果您针对的是 CPU 成本很重要的嵌入式设备,或者是能耗是首要问题的嵌入式/移动设备,那么 ARM 或 MIPS 可能更有意义。请记住,尽管您仍然需要处理处理轻松 3-4 倍大的代码所需的额外内存和二进制大小,并且您将无法接近性能。这是否重要在很大程度上取决于您将在其上运行什么。

于 2011-07-16T13:36:43.207 回答
11

x86 汇编语言还不错。当你得到机器代码时,它开始变得非常难看。指令编码、寻址模式等比大多数 RISC CPU 的要复杂得多。并且为了向后兼容的目的,内置了额外的乐趣——只有在处理器处于特定状态时才会启动。

例如,在 16 位模式下,寻址看起来非常奇怪。有一种寻址模式[BX+SI],但没有一种[AX+BX]。这样的事情往往会使寄存器的使用复杂化,因为您需要确保您的值在您可以根据需要使用的寄存器中。

(幸运的是,32 位模式更加理智(尽管有时本身仍然有点奇怪——例如分段),并且 16 位 x86 代码在引导加载程序和一些嵌入式环境之外基本上不再相关。)

还有过去的遗留物,当时英特尔试图让 x86 成为终极处理器。几个字节长的指令执行了实际上没有人再做的任务,因为坦率地说它们太慢或太复杂了。ENTER 和LOOP 指令,举两个例子——注意 C 栈帧代码对于大多数编译器来说就像“push ebp; mov ebp, esp”而不是“enter”。

于 2010-04-21T02:47:25.390 回答
4

我不是专家,但似乎人们不喜欢它的许多功能可能是它表现良好的原因。几年前,拥有寄存器(而不是堆栈)、寄存器帧等被视为使架构对人类更简单的很好的解决方案。然而,现在最重要的是缓存性能,x86 的可变长度字允许它在缓存中存储更多指令。我相信反对者指出的“指令解码”曾经占据了一半的芯片,现在几乎不再是这样了。

我认为并行性是当今最重要的因素之一——至少对于已经运行得足够快可以使用的算法而言。在软件中表达高并行性允许硬件摊销(或通常完全隐藏)内存延迟。当然,更远的架构未来可能是在量子计算之类的领域。

我从 nVidia 听说英特尔的错误之一是他们将二进制格式保持在硬件附近。CUDA 的 PTX 会进行一些快速的寄存器使用计算(图形着色),因此 nVidia 可以使用寄存器机而不是堆栈机,但仍有升级路径不会破坏所有旧软件。

于 2010-04-21T02:43:54.743 回答
3

除了人们已经提到的原因之外:

  • x86-16 有一个相当奇怪的内存寻址方案,它允许以多达 4096 种不同的方式寻址单个内存位置,将 RAM 限制为 1 MB,并迫使程序员处理两种不同大小的指针。幸运的是,向 32 位的迁移使这个特性变得不必要了,但是 x86 芯片仍然带有段寄存器的残渣。
  • 虽然不是 x86本身的错,但 x86 调用约定并没有像 MIPS 那样标准化(主要是因为 MS-DOS 没有附带任何编译器),给我们留下了__cdecl, __stdcall,__fastcall等的混乱。
于 2010-04-21T03:32:17.537 回答
3

如果您尝试编写针对 x86 的编译器,或者编写 x86 机器仿真器,或者即使您尝试在硬件设计中实现 ISA,我认为您将获得部分答案。

虽然我理解“x86 很丑!” 论点,我仍然认为编写 x86 程序集比 MIPS 更有趣(例如)——后者简直太乏味了。它总是对编译器而不是对人类好。我不确定如果芯片尝试过,它会对编译器编写者更具敌意......

对我来说最难看的部分是(实模式)分段的工作方式——任何物理地址都有 4096 个段:偏移量别名。你上次是什么时候需要的?如果段部分是 32 位地址的严格高位,事情会简单得多。

于 2010-05-14T16:41:15.213 回答
2
  1. x86 有一组非常非常有限的通用寄存器

  2. 它在最低级别(CISC 地狱)上提倡一种非常低效的开发风格,而不是一种有效的加载/存储方法

  3. 英特尔做出了一个可怕的决定,引入了明显愚蠢的段/偏移 - 内存寻址模型,以保持与(此时已经!)过时的技术兼容

  4. 在每个人都在使用 32 位的时候,x86 以微薄的 16 位阻碍了主流 PC 世界(其中大多数 - 8088 - 即使只有 8 位外部数据路径,这更可怕!) CPU


对我来说(我是一名 DOS 资深人士,从开发人员的角度来看每一代 PC!)第 3 点是最糟糕的。

想象一下我们在 90 年代初(主流!)的情况:

a) 由于遗留原因而受到严重限制的操作系统(640kB 易于访问的 RAM) - DOS

b)一个操作系统扩展(Windows),它可以在 RAM 方面做更多的事情,但在游戏等方面受到限制......并且不是地球上最稳定的东西(幸运的是后来改变了,但我'我在这里谈论90年代初)

c) 大多数软件仍然是 DOS,我们必须经常为特殊软件创建启动盘,因为有这个 EMM386.exe,一些程序喜欢,另一些则讨厌(尤其是游戏玩家 - 当时我是 AVID 游戏玩家 - 知道我在做什么在这里谈论)

d) 我们仅限于 MCGA 320x200x8 位(好吧,还有更多的特殊技巧,360x480x8 是可能的,但只有没有运行时库支持),其他一切都是混乱和可怕的(“VESA” - 哈哈)

e) 但在硬件方面,我们有 32 位机器,有相当多兆字节的 RAM 和 VGA 卡,支持高达 1024x768

造成这种糟糕情况的原因是什么?

英特尔的一个简单设计决策。机器指令级别(不是二进制级别!)与已经死去的东西的兼容性,我认为是 8085。其他看似无关的问题(图形模式等)是相关的,因为技术原因和非常狭窄x86 平台自带的思想架构。

今天,情况有所不同,但请询问任何汇编程序开发人员或为 x86 构建编译器后端的人。通用寄存器的数量非常少,不过是一个可怕的性能杀手。

于 2010-06-30T08:39:36.013 回答