[英]possible to do if (!boolvar) { … in 1 asm instruction?
这个问题更多的是出于好奇而非必要性:
是否有可能重写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版本,但我不知道如何以某种方式使用它,所以我可以一方面打开编译器优化,同时没有一个空的if
语句优化掉(或者有if语句与其内容一起优化,给出一些非通用的答案)
PS:当然我也搜索谷歌和SO这个,但有这么短的搜索条件我真的找不到任何有用的东西
PPS:我对一个语义上等效的版本没问题,这个版本不是语法等价的,例如不使用if
。
编辑:如果我对发出的asm指令的假设是错误的,请随时纠正我。
编辑2:我实际上已经学习了大约15年前,并在大约5年前重新学习了alpha架构,但我希望我的问题仍然足够明确,以弄清楚我在问什么。 此外,如果它有助于找到一个好的答案,你可以自由地假设消费者cpus中常见的任何类型的处理器扩展,直到AVX2(截至撰写本文时的当前haswell cpu)。
在我的帖子结束时,它将说明为什么你不应该针对这种行为(在x86上)。
正如Jerry Coffin所写,x86中的大多数跳转都取决于标志寄存器。
但有一个例外:如果ecx
/ rcx
寄存器为零,则跳转的j*cxz
指令集。 为此,您需要确保您的boolvar
使用ecx
寄存器。 您可以通过专门将其分配给该寄存器来实现
register int boolvar asm ("ecx");
但到目前为止,并非所有编译器都使用j*cxz
指令集。 icc
有一个标志可以做到这一点,但通常不建议这样做。 英特尔手册说明了两条指令
test ecx, ecx
jz ...
在处理器上更快。
这样做的原因是x86是CISC(复杂)指令集。 在实际的硬件中,虽然处理器会将在asm中作为一条指令出现的复杂指令拆分成多个微指令,然后以RISC方式执行。 这就是为什么并非所有指令都需要相同的执行时间的原因,有时多个小指令比一个指令要快。
test
和jz
是单个微指令,但是jecxz
将被分解为那两个。
存在j*cxz
指令集的唯一原因是,如果要在不修改标志寄存器的情况下进行条件跳转。
是的,这是可能的 - 但这样做取决于此代码发生的上下文。
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
我的假设是错的
你有几个假设是错误的。 首先你应该知道1条指令不一定比多条指令快。 例如,在较新的μarchs中, test
可以与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的答案一样直接跳转
另外86具有有条件的移动,因此,如果内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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.