5

这个问题更多是出于好奇而不是必要:

是否可以以某种方式重写 c 代码if ( !boolvar ) { ...,以便将其编译为 1 个 cpu 指令?

我已经尝试在理论层面上考虑这个问题,这就是我想出的:

if ( !boolvar ) { ...

需要先否定变量,然后根据该变量进行分支-> 2条指令(否定+分支)

if ( boolvar == false ) { ...

需要将 false 的值加载到寄存器中,然后根据该值进行分支 -> 2 条指令(加载 + 分支)

if ( boolvar != true ) { ...

需要将 true 的值加载到寄存器中,然后根据 -> 2 条指令进行分支(“如果不等于分支”)(加载 +“如果不等于分支”)

我的假设错了吗?有什么我忽略的吗?

我知道我可以生成程序的中间 asm 版本,但我不知道如何以某种方式使用它,因此我一方面可以打开编译器优化,同时没有if优化掉空语句(或者让if 语句与其内容一起优化,给出一些非通用的答案)

PS:当然我也为此搜索了google和SO,但是搜索词这么短,我真的找不到任何有用的东西

PPS:我可以使用语义上等效的版本,但语法上不等效,例如不使用if.


编辑:如果我对发出的 asm 指令的假设是错误的,请随时纠正我。


Edit2:我实际上在 15 年前学习了 asm,并在 5 年前重新学习了 alpha 架构,但我希望我的问题仍然足够清楚,可以弄清楚我在问什么。此外,如果它有助于找到一个好的答案,您可以自由假设消费 cpu 中常见的任何类型的处理器扩展,直到 AVX2(截至撰写本文时的当前 haswell cpu)。

4

3 回答 3

3

在我的帖子末尾,它会说明为什么你不应该针对这种行为(在 x86 上)。

正如 Jerry Coffin 所写,x86 中的大多数跳转都依赖于标志寄存器。

但是有一个例外:如果/寄存器为零j*cxz,则跳转的指令集。为此,您需要确保您使用寄存器。您可以通过专门将其分配给该寄存器来实现ecxrcxboolvarecx

register int boolvar asm ("ecx");

但到目前为止,并非所有编译器都使用这j*cxz组指令。有一个标志icc可以让它这样做,但通常不建议这样做。英特尔手册指出两条指令

test ecx, ecx
jz ...

在处理器上更快。

这样做的原因是 x86 是一个 CISC(复杂)指令集。在实际硬件中,尽管处理器会将在 asm 中作为一条指令出现的复杂指令拆分为多个微指令,然后以 RISC 样式执行这些微指令。这就是为什么并非所有指令都需要相同的执行时间,有时多条小指令比一条大指令快的原因。

testjz是单个微指令,但jecxz无论如何都会分解成这两个。

存在这组指令的唯一原因j*cxz是如果您想在不修改标志寄存器的情况下进行条件跳转。

于 2013-08-29T16:43:14.260 回答
1

我的假设错了吗

你有几个假设是错误的。首先,您应该知道 1 条指令不一定比多条指令快。例如,在较新的 μarchs中,test可以与 进行宏融合jcc,因此 2 条指令将作为一条指令运行。或者一个除法太慢了,可能同时已经完成了数十或数百个更简单的指令。如果 if 块比多条指令慢,那么将 if 块编译为单条指令是不值得的

此外,if ( !boolvar ) { ...不需要先否定变量,然后根据它进行分支。x86 中的大多数跳转都是基于标志的,并且它们同时具有是和否条件,因此无需否定该值。我们可以简单地跳到非零而不是跳到零

同样if ( boolvar == false ) { ...不需要将false 的值加载到寄存器中,然后根据它进行分支false是一个等于 0 的常数,它可以作为立即数嵌入到指令中(如cmp reg, 0)。但是对于检查零然后只是一个简单test reg, reg的就足够了。然后jnzjz将用于在零/非零上跳转,这将与上test一条指令融合为一个

可以制作if编译为单个指令的标头或正文,但这完全取决于您需要做什么以及使用什么条件。因为标志 forboolvar可能已经从上一条语句中获得,所以if下一行中的块可以使用它直接跳转,就像您在 Jerry Coffin 的回答中看到的那样

此外,x86 有条件移动,所以如果里面if是一个简单的赋值,那么它可以在 1 条指令中完成。下面是一个示例及其输出

int f(bool condition, int x, int y)
{
    int ret = x;
    if (!condition)
        ret = y;
    return ret;
}

f(bool, int, int):
        test    dil, dil ; if(!condition)
        mov     eax, edx ; ret = y
        cmovne  eax, esi ; if(condition) ret = x
        ret

在其他一些情况下,您甚至不需要有条件的移动或跳跃。例如

bool f(bool condition)
{
    bool ret = false;
    if (!condition)
        ret = true;
    return ret;
}

编译为单个xor而没有任何跳转

f(bool):
        mov     eax, edi
        xor     eax, 1
        ret

ARM 架构(v7 及更低版本)可以有条件地运行任何指令,因此可以只转换为一条指令

例如下面的循环

while (i != j)
{
   if (i > j)
   {
       i -= j;
   }
   else
   {
       j -= i;
   }
}

可以转换为 ARM 程序集为

loop:   CMP  Ri, Rj         ; set condition "NE" if (i != j),
                            ;               "GT" if (i > j),
                            ;            or "LT" if (i < j)
        SUBGT  Ri, Ri, Rj   ; if "GT" (Greater Than), i = i-j;
        SUBLT  Rj, Rj, Ri   ; if "LT" (Less Than), j = j-i;
        BNE  loop           ; if "NE" (Not Equal), then loop
于 2013-09-20T03:06:19.357 回答
1

是的,这是可能的——但这样做将取决于此代码发生的上下文。

x86 中的条件分支取决于标志寄存器中的值。为了将其编译为一条指令,其他一些代码已经需要设置正确的标志,所以剩下的就是一条指令,例如jnz wherever.

例如:

boolvar = x == y;
if (!boolvar) {
    do_something();
}

...最终可能呈现为:

    mov eax, x
    cmp eax, y    ; `boolvar = x == y;`
    jz @f
    call do_something
@@:

根据您的观点,它甚至可以只编译为指令的一部分。例如,相当多的指令可以“断言”,因此只有在某些先前定义的条件为真时才会执行它们。在这种情况下,您可能有一条指令将“boolvar”设置为正确的值,然后是一条有条件地调用函数的指令,因此没有一条(完整的)指令对应于if语句本身。

尽管您不太可能在写得体面的 C 语言中看到它,但一条汇编语言指令可能包含更多内容。对于一个明显的示例,请考虑以下内容:

    x = 10;
looptop:
    -- x;
    boolvar = x == 0;
    if (!boolvar)
        goto looptop;

整个序列可以编译成如下内容:

    mov ecx, 10
looptop:
    loop looptop
于 2013-08-29T16:28:46.990 回答