C/C++:正在使用比较结果作为int真正的无分支

C/C++ : is using the result of comparison as int really branchless?

本文关键字:int 结果 分支 比较 C++      更新时间:2023-10-16

我在许多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,这只会使程序集更清晰);编译器是变幻莫测的野兽。