为什么在x86操作系统上运行16位程序会变慢



我正在研究一些关于汇编的东西,以及我正在阅读的材料,作者说为16位编译的程序在x86操作系统上旋转得更慢,x64也是如此,32位编译的软件在x64上运行得更慢…

为什么会发生这种情况?计算机内存和处理器中发生了什么,使得程序16位或32位机器分别在32位和64位中旋转得更慢?

关于在32位系统中运行较慢的16位程序,我可以告诉你这一点。当英特尔从16位发展到32位时,他们必须扩展指令集来处理新的32位寄存器,但要保持与16位程序的二进制兼容性。

为了实现这一点,他们添加了一个前缀66h,如果我记得很清楚的话,当应用于任何使用16位寄存器的指令时,会使该指令使用32位寄存器。

例如,一条16位指令,如MOV AX、BX,前缀为66h,会变成MOV EAX、EBX

但这会对新的32位指令造成惩罚,因为它们至少需要一个额外的内存获取周期才能执行。英特尔随后创建了所谓的32位段和16位段。

基本上,任何一段代码都必须位于代码段中。在80386之前,所有段使用16位指令,并且假设所有指令使用16位寄存器。

Intel的32段也包含代码,但这一次假设每条指令都使用32位寄存器,因此在32位段中,MOV EAX、EBX的操作码与16位段中MOV AX、BX的操作码相同。

这允许程序不必为每个32位指令使用66h前缀。再也没有处罚了。

但是。。。如果我必须在一个包含在32位段中的程序中使用16位寄存器,该怎么办?那些使用16位寄存器的指令将不得不使用前缀66h。

因此:使用16位寄存器的指令在16位段中不固定,并以32位信号为前缀。使用32位寄存器的指令在32位段中不固定,并在16位段中加前缀。

此外:从奔腾处理器开始,我们有两条并行执行指令的管道。对于要使用的这些管道,输入它们的指令必须属于英特尔所称的"RISC核心":不再作为CPU内的微程序执行,而是使用有线逻辑的指令子集。你猜怎么着?前缀指令和使用16位寄存器在16位段中执行的代码不属于该组,因此不能与另一组并行执行。当一条带前缀的指令设法进入其中一条管道时,另一条管道将被暂停,从而影响CPU的性能。

关于"程序旋转更慢"。。。好程序不是"旋转"的,而是"被执行"的。如果你说的是钻头旋转指令。。。好碰巧8086有两个版本的位旋转指令:一个使用指定要旋转的位数的中间参数,另一个使用寄存器(通常是CX/ECX)来指定。

问题是8086处理器不允许中间参数使用除1以外的任何其他值(但CX/ECX中的值可以大于1)。80386及更高版本的处理器允许使用任何其他值作为中间操作数。此外,32位处理器只使用指定旋转量的操作数的低5位,因此操作不会超过31(将32位reigster旋转超过31次是没有意义的)。8086处理器没有强加这个限制,因此在操作中花费了更多的时间。

我真的不知道这是不是你的书中所说的"旋转得更慢"的意思。我记得旋转操作只能在其中一个管道中执行,而不能同时在两个管道中进行,因此两个连续的旋转指令不能配对。

我不确定你所说的旋转(组装操作?)是什么意思,但一般来说,这里可能有几个因素-

  1. CPU公司并没有真正致力于支持旧的遗留模式和ISA子集。x87就是一个很好的例子,任何不真正需要该精度级别的东西最好使用SSE/AVX执行性能关键任务,而不仅仅是因为矢量化。

  2. x86 CPU公司每次增加寄存器大小时,都会保留旧的寄存器集,只为较长的版本添加逻辑名称。兼容性的需要要求旧的操作仍然可以在相同的寄存器上工作,因此您现在可以在同一程序中写入ah/al、ax、eax和rax。在其中一些情况下(即8bit/16bit部分),这种兼容性要求CPU在只写入寄存器的下部时保持寄存器的上部完整,这样做会隐含地引入合并操作,这可能会导致速度减慢。更糟糕的是,您可能会引入错误的依赖关系,因为每次写入16位寄存器都需要合并早期操作留下的上部。

另请参阅此处-为什么大多数x64指令将32位寄存器的上部清零

最新更新