处理器如何处理条件?

时间:2009-03-25 19:42:41

标签: x86 conditional-statements

那么,SUPER低级别的IF()看起来是什么样的,x86处理器是如何处理的呢?

7 个答案:

答案 0 :(得分:13)

处理器具有“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指令可以根据条件标志跳转或不跳转。

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

正如moocha在评论中指出的那样,某些架构允许您对一些,许多甚至所有指令应用条件,因此您可能不仅有'branch if'指令,还有'和if','add如果','移动,如果'等。

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

这是另一个问题的话题,但是......

- 亚当

答案 1 :(得分:5)

使用C编译器的输出(使用gcc上的-S开关)相当容易,看看C的给定片段在编译时会产生什么输出。在玩具程序上使用优化时要小心。如果您不小心,优化器通常会优化掉总是以某种方式变化的条件(有关更详细的说明,请参阅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 > ii \n”声明一个常量

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

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

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

答案 2 :(得分:2)

这是一个分支指令,取决于特定的机器架构。它弄清楚如何设置一个内存位置或寄存器来测试特定的低级别条件 - 如分支 - 如果不相等或分支 - 如果不是零,... - 那么测试然后跳转(或者如果条件失败则不会到另一部分记忆。显然,如果你有一个复杂的条件,它可能需要评估许多不同的条件,并可能涉及几个分支指令。

答案 3 :(得分:1)

通常,CPU具有所谓的指令寄存器,该寄存器保存下一个要执行的当前机器语言操作码的存储器地址......以及用于保存数据的许多其他寄存器。

通常,在cpu执行指令寄存器中的每个操作码之后,它只是将其递增1以移动到内存中的下一个位置,该位置应该在编译的程序应用程序中具有下一个操作码。

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

那就是“低”水平,因为它可以说它没有谈论继电器和晶体管......

答案 4 :(得分:0)

以下是对这些结构如何在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程序员可以有条件地指定任何指令,这会大大减少分支。

答案 5 :(得分:0)

尽管大多数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

可以转换为没有任何分支的机器代码

答案 6 :(得分:-1)

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

编辑:我似乎应该稍微含糊一点地解释。跟我一起,我主修计算机科学,而不是电气工程,所以我对这些事情没有很深刻的理解:

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

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