【问题标题】:How does the processor handle conditions?处理器如何处理条件?
【发布时间】:2009-03-25 19:42:41
【问题描述】:

那么,超低级的 IF() 是什么样的,x86 处理器如何处理它?

【问题讨论】:

  • 您的问题在当前的版本中难以置信地含糊不清。你怎么能指望任何人甚至远程开始回答它?实际上有数百种处理器架构。有些甚至不是冯诺依曼机器。请详细说明。
  • 你的意思是在装配层面,还是低于装配层面?
  • @moocha,标签有点幼稚。我补充说我正在处理 x86 架构处理器。
  • 感谢您的澄清。不过,我对“幼稚”的评论感到生气。我认为将模糊问题标记为模糊没有任何问题。

标签: x86 conditional-statements


【解决方案1】:

处理器具有“Branch if”指令,当满足某个条件时它会分支,否则它会继续执行下一条指令。

所以

if(A)
{
    dosomething;
}

会变成

load A into register 0
if the zero flag is set (ie, register 0 contains 0x00) then jump to endcondition)
dosomething
endcondition:

更复杂的条件 (if(A || B && C)) 变成一系列指令,使寄存器处于 0 或非零状态,因此 branchif 指令可以根据条件标志跳转或不跳转。

有许多条件标志(零、进位、负数、溢出等),并且一些 branchif 指令还可以在更复杂的条件下运行(即,它实际上可能会检查一个寄存器是否等于另一个寄存器,而不是只是看标志)。每种架构都是不同的,并且需要权衡取舍,因此指令集是完整的,而且速度快且紧凑。

正如 moocha 在 cmets 中指出的那样,某些架构允许您对一些、许多甚至所有指令应用条件,因此您可能不仅有 'branch if' 指令,还有 'and if', 'add if'、'move if' 等

一旦您进入流水线、乱序执行、缓存、微代码和所有其他高级主题,除了这个简单的解释之外,x86 非常、非常、非常复杂。对于大多数目的,上述解释就足够了。但是,如果您正在编写一个手工制作的非常非常紧密的算法,则必须将这些因素考虑在内,以获得最大的性能和吞吐量。

不过,这是另一个问题的主题......

-亚当

【讨论】:

  • 这不一定成立。想到的第一个反例是 ARM 架构,您通常会在指令前面使用条件代码而不是分支。
  • 这是一个很好的观点 - 一些架构允许您对许多(如果不是大多数)指令设置条件。但是,这会在代码中浪费大量空间,因为您的大部分指令都将无条件运行。
  • 现在添加一个关于流水线和缓存的讨论,你已经得到了完美的答案。
  • 没错,你绝对可以使用 4b 前缀:) 当然,我是在挑选尼特。
  • 我想说,它现在已经非常接近完美了:)
【解决方案2】:

使用 C 编译器的输出(使用 gcc 上的-S 开关)来查看给定的 C 的 sn-p 在编译时会生成什么输出是相当容易的。不过,在玩具程序上使用优化时要小心。如果您不小心,优化器通常会优化掉总是以某种方式运行的条件(有关更详细的说明,请参阅this article on microbenchmarks)。

例如,一个简单的 C 程序:

#include <stdio.h>

int main (int argc, char **argv) {
    int ii = 10;
    int jj = 20;
    if (jj > ii) {
        puts ("jj > ii \n");
    }
    return 0;
}

编译成以下汇编语言:

    .file   "foo.c"
    .section    .rodata
.LC0:
    .string "jj > ii \n"
    .text
.globl main
    .type   main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ecx
    subl    $20, %esp
    movl    $10, -8(%ebp)
    movl    $20, -12(%ebp)
    movl    -12(%ebp), %eax
    cmpl    -8(%ebp), %eax
    jle .L2
    movl    $.LC0, (%esp)
    call    puts
.L2:
    movl    $0, %eax
    addl    $20, %esp
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.3.2-1ubuntu12) 4.3.2"
    .section    .note.GNU-stack,"",@progbits

简要剖析正在发生的事情:

  • 第一部分(.rodata)声明了一个带有字符串'jj &gt; ii \n'的常量

  • 第二部分是初始化堆栈上的iijj 变量的内容。

  • 来自cmpl -8(%ebp), %eax 的位正在进行实际比较; jle 指令跳过了对“puts”的调用,这实际上是“if”语句的逻辑反转。

  • 在标签“.L2”之后,系统正在整理堆栈顶部并从调用中返回。

【讨论】:

    【解决方案3】:

    这是一个分支指令,取决于特定的机器架构。它计算出如何设置内存位置或寄存器来测试特定的低级条件 - 例如分支如果不等于或分支如果不为零,...... - 然后该测试是否会跳转(或者如果条件失败则不这样做)到内存的另一部分。显然,如果您有一个复杂的条件,它可能需要评估许多不同的条件,并且可能涉及多个分支指令。

    【讨论】:

      【解决方案4】:

      一般来说,CPU 有一个指令寄存器,它保存着当前机器语言操作码的内存地址,接下来要执行...以及许多其他寄存器来保存数据。

      通常,cpu 执行指令寄存器中的每个操作码后,它会简单地将其加一以移动到内存中的下一个位置,该位置应该在已编译的程序应用程序中具有下一个操作码。

      一个操作码(实际上可能有几个),但是通过“比较”其他两个 cpu 寄存器中的值,它允许 cpu “分支”,如果一个大于另一个,它会将一个内存地址复制到指令寄存器,而如果另一个是最大的,它会将第二个不同的内存地址复制到指令寄存器中。

      这是关于“低”级别的,因为它可以说是没有谈论继电器和晶体管......

      【讨论】:

        【解决方案5】:

        下面是关于如何在 x86 架构上编译此类结构的一个很好的概述:http://en.wikibooks.org/wiki/X86_Disassembly/Branches#If-Then

        有时有一些方法可以避免分支(由于管道中断,这通常会对性能产生严重的负面影响)。例如,从 i686 开始的指令集(从 Pentium Pro 到今天的所有指令)都有一个条件移动指令,它可能会编译这个:

        if (a==0) {
            b= 1;
        }
        

        到这样的事情:

        cmp    0, eax
        cmovzl ebx, 1
        

        没有分支,只要您的编译器设置为针对 i686+(感觉就像这样;编译器复杂且难以理解)。 SET[condition] 是另一个类似的条件指令。

        幸运的老 ARM 程序员可以有条件地指定任何指令,这大大减少了分支。

        【讨论】:

          【解决方案6】:

          尽管大多数 if 语句最终都会成为条件分支,但对于两个分支都没有任何副作用的非常简单的情况,优化编译器可能会生成执行这两个语句并计算结果的代码,而不是只执行一个。这在流水线架构中具有优势,因为分支预测未命中,计算两个分支的平均成本低于平均成本。

          例如代码:

          int x;
          
          if ( y < 5 )
            x = 5;
          else
            x = y;
          

          可以像编写一样编译:

          y -= 5
          int r = y < 0; // r is 1 if y < 5, 0 otherwise
          r -= 1         // r is 0x00000000 if y < 5, 0xffffffff otherwise
          x = y & r      // x is 0 if y < 5, (y-5) otherwise 
          x += 5;        // x is 5 if y < 5, y otherwise
          

          无需任何分支即可转换为机器码

          【讨论】:

            【解决方案7】:

            基本上,您有一堆电子在 CPU 内部的各种原子之间传递。由于 CPU 中硅原子的结构,电子遵循特定路径,这决定了计算机将遵循的执行分支。

            编辑:看来我应该解释得不那么含糊了。忍耐一下,我学的是计算机科学,不是电气工程,所以我对这些东西的理解不是很深:

            您的 CPU 由一种材料制成,通常是硅,称为“半导体”。半导体的一大优点是它们的电气特性可以通过“掺杂”轻松改变,或者应用杂质在材料上形成负或正“电荷载流子”区域。这些区域汇合在一起的线路被称为“交汇点”,电流通过这些交汇点的一种方式比另一种更容易。这一特性被用来制造二极管,它允许电流仅在一个方向上流动,以及晶体管,它可以被认为是允许一个电流控制另一个电流的微型开关。这些晶体管和二极管以多种方式组合在一起,以创建 CPU 的逻辑门。

            CPU 内部的许多逻辑门都专门用作“控制单元”,它负责检索和解码指令,告诉 CPU 的其余部分该做什么,并最终获得下一条指令。在 x86 上,控制单元实际上正在运行“微代码”,它告诉它如何处理分支、流水线等。您确实需要非常具体地了解特定的处理器系列,才能了解 x86 ISA 如何在特定的微架构上实现。

            【讨论】:

            • 但它似乎很空洞,(由于含糊不清)“由于硅原子的结构......” ????
            • +1 因为它在技术上是正确的,但是这比我想要的有点“低级”。
            • 我不确定我是否称之为技术上正确;晶体管由非常不纯的硅制成,正是硅和杂质的组合导致电子像它们一样流动。它也是无可救药的模糊,并且愉快地一次跳跃了许多抽象层。
            • 我已经让它变得不那么模糊了。它仍然兴高采烈地跳过了许多抽象层,但现在它使用了两个或三个边界。
            • 这实际上是最“低级”的答案。其他答案是指处理器顶部的执行逻辑,而不是它的物理工作方式。 +1
            猜你喜欢
            • 2019-04-29
            • 2012-03-26
            • 1970-01-01
            • 2012-03-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多