这个问题更多的是出于好奇而非必要:
是否可以以某种方式重写c代码if ( !boolvar ) { ...
,以便将其编译为1 cpu指令?
我试过在理论层面考虑这个问题,这就是我想出的:
if ( !boolvar ) { ...
需要首先否定变量,然后根据该变量进行分支 - > 2条指令(否定+分支)
if ( boolvar == false ) { ...
需要将false值加载到寄存器中,然后根据该值进行分支 - > 2条指令(加载+分支)
if ( boolvar != true ) { ...
需要将true的值加载到寄存器中然后根据该分支(“branch-if-not-equal”)分支 - > 2条指令(load +“branch-if-not-equal”)
我的假设错了吗?我有什么东西可以忽略吗?
我知道我可以生成程序的中间asm版本,但我不知道如何以某种方式使用它,所以我一方面可以打开编译器优化,同时又没有空{{1} }语句优化了(或者将if语句与其内容一起优化,给出一些非通用的答案)
P.S。:当然我也搜索谷歌和SO这个,但有这么短的搜索条件我真的找不到任何有用的东西
P.P.S。我对一个语义上等效的版本没问题,这个版本不是语法等价的,例如不使用if
。
编辑:如果我对发出的asm指令的假设是错误的,请随时纠正我。
Edit2:我实际上已经学习了大约15年前,并在大约5年前重新学习了alpha架构,但我希望我的问题仍然足够明确,以弄清楚我在问什么。此外,如果它有助于找到一个好的答案,你可以自由地假设消费者cpus中常见的任何类型的处理器扩展,直到AVX2(截至撰写本文时的当前haswell cpu)。
答案 0 :(得分:3)
在我的帖子结束时,它会说明为什么你不应该针对这种行为(在x86上)。
正如Jerry Coffin写的那样,x86中的大多数跳转取决于标志寄存器。
但有一个例外:j*cxz
指令集在ecx
/ rcx
寄存器为零时跳转。为此,您需要确保boolvar
使用ecx
注册。您可以通过专门将其分配给该寄存器来实现这一目标
register int boolvar asm ("ecx");
但到目前为止,并非所有编译器都使用j*cxz
指令集。 icc
有一个标志可以让它做到这一点,但通常不建议这样做。英特尔手册说明了两条指令
test ecx, ecx
jz ...
在处理器上更快。
这样做的原因是x86是CISC(复杂)指令集。在实际的硬件中,虽然处理器会将在asm中作为一条指令出现的复杂指令拆分成多个微指令,然后以RISC方式执行。这就是为什么并非所有指令都需要相同的执行时间的原因,有时多个小指令比一个指令更快。
test
和jz
是单个微指令,但jecxz
将被分解为这两个。
存在j*cxz
指令集的唯一原因是,如果要在不修改标志寄存器的情况下进行条件跳转。
答案 1 :(得分:1)
是的,它是可能的 - 但这样做取决于此代码的上下文。
x86中的条件分支取决于flags寄存器中的值。为了将其编译成单个指令,其他一些代码将需要设置正确的标志,因此剩下的所有内容都是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
答案 2 :(得分:1)
我的假设是错误的
你有几个假设错了。首先你应该知道1条指令不一定比多条指令快。例如,在较新的μarchtest
可以与jcc
进行宏融合,因此2条指令将作为一条运行。或者划分是如此之慢,以至于可能已经完成了数十或数百个更简单的指令。如果if块比多个指令慢,那么将if块编译为单个指令是不值得的
此外,if ( !boolvar ) { ...
不需要 首先否定变量,然后根据 进行分支。 x86中的大多数跳转都基于标志,它们同时具有yes和no条件,因此不需要否定该值。我们可以简单地跳到非零而不是跳零(
同样地,if ( boolvar == false ) { ...
不需要 将false值加载到寄存器中,然后根据 进行分支。 false
是一个等于0的常量,可以作为立即数嵌入到指令中(如cmp reg, 0
)。但是对于零检查,那么只需一个简单的test reg, reg
即可。然后jnz
或jz
将用于跳转零/非零,这将与之前的test
指令融合为一个
可以将if
标题或正文编译成单个指令,但这完全取决于您需要做什么,以及使用了什么条件。因为boolvar
的标志可能已经可以从前一个语句中获得,所以下一行中的if
块可以像Jerry Coffin的回答一样直接跳转
此外x86具有条件移动,因此如果if
内部是一个简单的赋值,则可以在1条指令中完成。以下是example and its output
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;
}
编译为a single xor
without any jump at all
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