我在许多SO答案中看到过这种代码:
template <typename T>
inline T imax (T a, T b)
{
return (a > b) * a + (a <= b) * b;
}
哪里的作者说这是无枝的。
但这真的是当前架构的无分支吗?(x86、ARM…)有没有一个真正的标准保证这是无分支的?
x86有SETcc
系列指令,根据标志的值将字节寄存器设置为1或0。编译器通常使用它来实现这种没有分支的代码。
如果你使用"天真"的方法
int imax(int a, int b) {
return a > b ? a : b;
}
编译器将使用CMOVcc
(条件移动)指令族生成更高效的无分支代码。
ARM能够有条件地执行每一条指令,使编译器能够高效地编译您的和幼稚的实现,幼稚的实现更快。
我偶然发现了这个SO问题,因为我问了我同样的问题……事实证明并不总是这样。例如,以下代码…
const struct op {
const char *foo;
int bar;
int flags;
} ops[] = {
{ "foo", 5, 16 },
{ "bar", 9, 16 },
{ "baz", 13, 0 },
{ 0, 0, 0 }
};
extern int foo(const struct op *, int);
int
bar(void *a, void *b, int c, const struct op *d)
{
c |= (a == b) && (d->flags & 16);
return foo(d, c) + 1;
}
…在所有优化级别中使用gcc 3.4.6(i386)和8.3.0(amd64,i386)编译为分支代码。3.4.6中的那个更容易手动阅读,我将用gcc -O2 -S -masm=intel x.c; less x.s
:进行演示
[…]
.text
.p2align 2,,3
.globl bar
.type bar , @function
bar:
push %ebp
mov %ebp, %esp
push %ebx
push %eax
mov %eax, DWORD PTR [%ebp+12]
xor %ecx, %ecx
cmp DWORD PTR [%ebp+8], %eax
mov %edx, DWORD PTR [%ebp+16]
mov %ebx, DWORD PTR [%ebp+20]
je .L4
.L2:
sub %esp, 8
or %edx, %ecx
push %edx
push %ebx
call foo
inc %eax
mov %ebx, DWORD PTR [%ebp-4]
leave
ret
.p2align 2,,3
.L4:
test BYTE PTR [%ebx+8], 16
je .L2
mov %cl, 1
jmp .L2
.size bar , . - bar
指针比较操作调用了一个比较,甚至调用了一个子例程来插入1。
即使不使用!!(a == b)
也会带来不同。
tl;dr
检查实际编译的实际编译器输出(使用-S
进行汇编或使用objdump -d -Mintel x.o
进行反汇编;如果不在x86上,则删除-Mintel
,这只会使程序集更清晰);编译器是变幻莫测的野兽。