为什么x86难看?为什么与其他人相比,它被认为是劣等的?

cla*_*aws 101 x86 assembly x86-64 mips cpu-architecture

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

还有更多的评论

我试过搜索,但没有找到任何理由.我发现x86不好可能因为这是我熟悉的唯一架构.

有人可以给我一些考虑x86丑陋/坏/劣等的理由.

Bil*_*eal 89

几个可能的原因:

  1. x86是一个相对较老的ISA(毕竟它的祖先是8086s)
  2. x86已经发展了好几次,但硬件需要保持与旧二进制文件的向后兼容性.例如,现代x86硬件仍然支持本机运行16位代码.另外,存在若干存储器寻址模型以允许较旧的代码在同一处理器上互操作,例如实模式,保护模式,虚拟8086模式和(amd64)长模式.这可能会让一些人感到困惑.
  3. x86是CISC机器.很长一段时间,这意味着它比像MIPS或ARM这样的RISC机器要慢,因为指令具有数据相互依赖性和标志,使得大多数形式的指令级并行性难以实现.现代实现将x86指令转换为类似RISC的指令,称为" 微操作 ",以使这些优化在硬件中实现.
  4. 在某些方面,x86并不逊色,它只是不同.例如,输入/输出在绝大多数体系结构上作为内存映射处理,但不在x86上处理.(NB:现代x86上通常具有某种形式的DMA支持,并通过存储器映射其它硬件进行通信;但是ISA仍具有如I/O指令INOUT)
  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--;
    
    Run Code Online (Sandbox Code Playgroud)

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

    虽然MIPS(和类似架构)的简单性并不一定使它们更优越,但是对于汇编类汇编课程的教学,从更简单的ISA开始是有意义的.一些汇编类教导了一个名为y86的超简化x86子集,它被简化为超出了对实际使用无用的点(例如没有移位指令),或者一些只教授基本的x86指令.

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

编辑:这不应该是一个bash x86!派对.考虑到问题的措辞,我别无选择,只能进行一些抨击.但除了(1)之外,所有这些事情都是有充分理由的(见评论).英特尔设计师并不愚蠢 - 他们希望通过他们的架构来实现一些东西,而这些是他们为实现这些目标而必须付出的一些税.

  • 这是一个权衡.它的优势在于二进制大小可能更小,但它是一个弱点,因为您需要使用非常复杂的硬件来为这些指令实现解析器.绝大多数指令都是相同的大小 - x86上可变长度操作码的大部分原因是当他们决定添加功能时发现它们无法表示他们想要的位数与他们必须使用的位数.绝大多数人并不关心二进制大小,几乎与硬件复杂性或功耗一样多. (17认同)
  • @Joey Adams:将x86的可变长度指令与ARM的Thumb模式(http://en.wikipedia.org/wiki/ARM_architecture#Thumb)进行对比.Thumb模式导致ARM的目标代码明显变小,因为较短的指令直接映射到普通指令.但由于较大指令与较小指令之间存在1:1映射,因此解析硬件易于实现.x86的可变长度指令没有这些好处,因为它们首先没有这样设计. (8认同)
  • (6)不是每个程序都需要使用每个操作码,但是当我需要SSE3时,我很高兴我拥有它. (7认同)
  • 这是使x86指令设置如此丑陋的原因之一,因为它无法确定它是累加器还是基于寄存器文件的架构(尽管这主要是通过386修复,这使得指令集更加正交,无论68k球迷告诉你什么). (5认同)
  • @Chris Kaminski:这怎么不影响硬件?当然,在一台现代化的全尺寸电脑上,没有人会关心,但如果我正在制作类似手机的东西,我更关心耗电量而不是其他任何东西.可变长度操作码不会增加执行时间,但解码硬件仍需要电源才能运行. (4认同)
  • 我看到可变长度的操作码是强度的来源,例如,x86机器码往往比PowerPC代码占用更少的空间.我可能错了. (2认同)
  • 它被一堆不是高速缓存的热晶体管或任何其他有用的晶体管转化为风险. (2认同)
  • re:注册重命名:甚至具有许多架构寄存器(例如32)的RISC ISA的实现仍然使用寄存器重命名来维持具有大量飞行指令的大型无序窗口.当没有足够的*架构*寄存器来保存regs中的所有有用值时,架构寄存器的稀缺程度会受到额外的存储/重新加载指令.许多与几个架构寄存器与具有寄存器重命名的小型或大型物理寄存器文件不同.你仍然使用reg.在高性能执行中重命名任何ISA. (2认同)

dth*_*rpe 25

在我看来,对x86的主要打击是它的CISC起源 - 指令集包含许多隐含的相互依赖性.这些相互依赖性使得难以对芯片上的指令重新排序这样做,因为必须为每个指令保留这些相互依赖性的工件和语义.

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

在RISC架构中,add指令将指定输入操作数和输出寄存器,并且仅使用那些寄存器进行有关操作的所有操作.这使得分离彼此接近的添加操作变得更加容易,因为没有bloomin'标志注册强制所有内容排队并执行单个文件.

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

无论如何,超过3或4代Alpha AXP芯片设计,硬件从具有32个int寄存器和32个浮点寄存器的spartan指令集的文字实现变为具有80个内部寄存器的大规模乱序执行引擎,寄存器重命名,结果转发(将前一条指令的结果转发给后来依赖于该值的指令)以及各种狂野和疯狂的性能提升器.所有这些花里胡哨的东西,AXP芯片芯片仍然比当时的奔腾芯片芯片小得多,而AXP的速度要快得多.

你不会在x86系列树中看到那些性能提升的东西很大程度上是因为x86指令集的复杂性使得许多类型的执行优化成本过高,如果不是不可能的话.英特尔的天才是放弃在硬件上实现x86指令集 - 所有现代x86芯片实际上都是RISC核心,在某种程度上解释了x86指令,将它们转换为内部微码,保留了原始x86的所有语义指令,但允许一点RISC乱序和微码的其他优化.

我编写了很多x86汇编程序,可以充分理解其CISC根本的便利性.但是我没有完全理解x86是多么复杂,直到我花了一些时间编写Alpha AXP汇编程序.我对AXP的简洁和统一感到惊讶.差异是巨大而深刻的.

  • 除非你能解释m68k,否则我会听取CISC*本身的抨击*. (6认同)
  • 我不认为这个答案已经足够糟糕了,但我确实认为整个"RISC比CISC更小,更快"的论点在现代时代并不是真正相关的.当然,AXP可能在一段时间内变得更快,但事实上现代RISC和现代的CISC在性能方面差不多.正如我在回答中所说的那样,x86解码的轻微功率损失是不使用x86用于移动电话之类的原因,但对于全尺寸桌面或笔记本来说,这是一个小小的争论. (3认同)
  • @Billy:大小不仅仅是代码大小或指令大小.英特尔在芯片表面区域中付出相当大的代价来实现所有这些特殊指令的硬件逻辑,RISC微代码核心是否在引擎盖下.模具的尺寸直接影响制造成本,因此它仍然是现代系统设计的有效关注点. (3认同)
  • 我不熟悉m68k,所以我不能批评它. (2认同)

sta*_*san 20

x86架构可以追溯到8008微处理器和亲属的设计.这些CPU是在内存缓慢的时候设计的,如果你可以在CPU裸片上进行设​​计,它通常快得多.但是,CPU芯片空间也很昂贵.这两个原因是为什么只有少数寄存器具有特殊用途,以及具有各种陷阱和限制的复杂指令集.

来自同一时代的其他处理器(例如6502系列)也有类似的限制和怪癖.有趣的是,8008系列和6502系列都是嵌入式控制器.即使在那时,嵌入式控制器也可以用汇编程序进行编程,并且在很多方面都适合汇编程序员而不是编译器编写器.(看看VAX芯片,当你迎合编译器编写时会发生什么.)设计人员不希望它们成为通用计算平台; 那就像POWER架构的前辈那样.当然,家庭计算机革命改变了这一点.

  • +1这是唯一一个实际上似乎有问题历史背景的人的答案. (4认同)
  • 在8086年左右,内存速度几乎达到与CPU相近的奇偶校验.德州仪器(TI)的9900设计只有这样才有效.但随后CPU再次向前跑,并留在那里.只有现在,有缓存来帮助管理这个. (4认同)
  • 记忆一直很慢.今天可能(相对而言)比我在1982年开始使用Z80s和CP/M时更慢.灭绝并不是进化的唯一途径,因为灭绝会导致特定的进化方向停止.我会说x86在其28年(迄今为止存在)中已经很好地适应了. (3认同)
  • @Olof Forshell:它与汇编程序兼容,因为8080汇编代码可以转换为8086代码.从这个角度来看,它是8080加上扩展,就像你可以将8080视为8008加上扩展一样. (3认同)
  • @Olof Forshell:除了8086是为了实现这一目的而设计的.它是8080的扩展,并且大多数(可能全部)8080指令一对一映射,具有明显相似的语义.无论您想以何种方式推动它,IBM 360架构都不是这样. (3认同)
  • @Olof Forshell:遗憾的是,我的所有来源都已被处理掉很久,但我有点关注当时的情况,并且没有从维基百科获取信息.我对8080有一个合理的熟悉程度,对8086的熟悉程度较低,所以它对我来说非常真实.我确实读过原来的BASIC很慢,因为它是从8080转换而来的. (2认同)
  • @Olof Forshell:仅仅因为维基百科说"以......销售"并不意味着它是错误的.营销材料比任何技术更容易找到和引用.此外,只要我们执行维基百科的解释,就会说"8086是设计的",这意味着处理器的设计旨在促进ASM级兼容性(尽管不是二进制兼容性). (2认同)

Olo*_*ell 12

我还有一些其他方面:

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

  mov eax,b
  xor edx,edx
  div dword ptr c
  mov a,eax
Run Code Online (Sandbox Code Playgroud)

作为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
Run Code Online (Sandbox Code Playgroud)

这里通常不会有余数.

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

利弊:

RISC指令可以与周围的代码混合以改进指令调度,这对于x86来说是不太可能的,而x86则在CPU本身内部(或多或少地取决于序列)工作.上面的RISC序列通常在32位架构上长28个字节(7个32位/ 4个字节宽度的指令).这将导致片外存储器在获取指令时工作更多(七次读取).更密集的x86序列包含更少的指令,虽然它们的宽度不同,但你可能也会看到平均4字节/指令.即使你有指令缓存加速这七次,也意味着与x86相比,你可以在其他地方减少三次补偿.

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

从更个人的角度来看,我发现编写RISC程序集比编写x86更困难.我通过在C中编写RISC例程来编译和修改生成的代码来解决这个问题.从代码生产的角度来看,这更有效,从执行的角度来看效率可能更低.所有这32个寄存器都要跟踪.对于x86,它是另一种方式:6-8个具有"真实"名称的寄存器使问题更易于管理,并使得生成的代码能够按预期工作更有信心.

丑陋?这是旁观者的眼睛.我更喜欢"与众不同".

  • 很棒的反点! (2认同)
  • 这不是我第一次听到用C语言编写它的建议,然后将其提炼成汇编程序.这肯定有帮助 (2认同)

R..*_*R.. 9

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

  1. 在许多情况下,这只是部分正确或根本不正确.x86中最有用的"复杂"指令mov %eax, 0x1c(%esp,%edi,4)就是寻址模式,这些都没有细分.
  2. 在现代机器上通常更重要的不是花费的周期数(因为大多数任务不是cpu绑定的)而是代码的指令缓存影响.5-6固定大小(通常是32位)指令将对缓存产生很大的影响,这些指令很少超过5个字节.

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

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

另一方面,如果您的目标是CPU成本高的嵌入式设备,或者能耗最受关注的嵌入式/移动设备,ARM或MIPS可能更有意义.请记住,虽然你仍然需要处理处理代码所需的额外内存和二进制大小,这些代码很容易大3-4倍,但你将无法接近性能.这是否重要取决于您将在其上运行的内容.

  • _在能耗最重要的地方,ARM或MIPS可能更有意义_...因此,如果至少有一个方面使ARM或MIPS更有意义,这是否会使x86 **不一定**成为最佳ISA。 ? (2认同)
  • @RocketRoy*"英特尔i860 CPU.英特尔再也没有去过那里."*经过一番研究,i860听起来很像*Itanium:VLIW,编译器排序指令并行...... (2认同)

cHa*_*Hao 8

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

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

(幸运的是,32位模式更加安全(虽然有时候仍然有点奇怪 - 例如分段),并且16位x86代码在引导加载程序和某些嵌入式环境之外已经基本上无关紧要了.)

还有来自过去的剩余时间,当时英特尔试图让x86成为终极处理器.说明几个字节长,执行任何人实际上不再执行的任务,因为他们坦率地说太慢或者复杂.对于两个示例,请参阅ENTER和LOOP指令 - 注意C堆栈帧代码类似于"push ebp; mov ebp,esp",而不是大多数编译器的"输入".

  • 当我被迫使用基于x86的机器并开始查看它(具有m68k背景)时,我开始觉得编程令人沮丧,...就像我用C语言学习编程,然后是被迫与asm取得联系...你"感觉"你失去了表达能力,轻松,清晰,"连贯","直觉".我相信如果我开始使用x86进行编程,我会想到它不是那么糟糕......也许......我也做了MMIX和MIPS,他们的"asm lang"远胜于x86(如果这是Q的正确PoV,但也许不是) (4认同)
  • 我相信"输入"与"推/移"问题的出现是因为在某些处理器上,"push/mov"更快.在某些处理器上,"输入"更快.这就是生活. (2认同)

归档时间:

查看次数:

26033 次

最近记录:

8 年,1 月 前