提问人:galinette 提问时间:12/8/2015 最后编辑:user16217248galinette 更新时间:11/13/2023 访问量:1779
将比较结果用作 int 真的是无分支吗?
Is using the result of comparison as int really branchless?
问:
我在很多答案中都看到过这样的代码,作者说这是无分支的:
template <typename T>
inline T imax (T a, T b)
{
return (a > b) * a + (a <= b) * b;
}
但是,在当前的架构上,这真的是无分支的吗?(x86,ARM... 有没有真正的标准保证这是无分支的?
答:
7赞
fuz
12/8/2015
#1
x86 具有一系列指令,这些指令根据标志的值将字节寄存器设置为 1 或 0。编译器通常使用它来实现这种没有分支的代码。SETcc
如果您使用“幼稚”方法
int imax(int a, int b) {
return a > b ? a : b;
}
编译器将使用(条件移动)系列指令生成更高效的无分支代码。CMOVcc
ARM 能够有条件地执行每条指令,这使得编译器能够有效地编译 U 和朴素的实现,朴素的实现速度更快。
评论
3赞
Lundin
12/8/2015
换句话说:通过尝试手动优化代码,而不是编写尽可能可读和简单的代码,代码变得更丑陋和更慢。
0赞
SergeyA
12/8/2015
这并不奇怪。根据定义,编译器编写者通常比 C++ 程序员更了解芯片组指令。
0赞
galinette
12/8/2015
对于平台部分来说,这是一个很好的答案,但是标准中真的可以保证无分支吗?我想使用 if/else 可以作为您的“幼稚”方法,这基本上意味着我引用的这些伪优化答案与 std 函数(最小值、最大值等)相比会降低性能
0赞
fuz
12/8/2015
@galinette我不熟悉 C++ 标准,但如果它像 C 标准一样,它现在绝对可以保证某些东西是如何实现的。从标准的角度来看,甚至加法也可以通过分支来实现。
0赞
fuz
12/9/2015
@galinette 我删除了您在答案中添加的句子,因为我不熟悉C++标准,也不想对此做出任何声明。
0赞
mirabilos
8/3/2019
#2
我偶然发现了这个 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的;博士
检查实际编译的实际编译器输出(使用或反汇编;如果不在 x86 上,则删除,它只会使汇编更清晰);编译器是不可预测的野兽。-S
objdump -d -Mintel x.o
-Mintel
评论
0赞
fuz
9/28/2020
请注意,如果编译为 32 位,则可能需要指定足够新的体系结构(例如),因为所需的无分支指令是在大约 25 年前才添加的。-march=i686
1赞
fuz
9/28/2020
请注意,在这种特定情况下,不能使用无分支代码,因为编译器不能假设允许取消引用 if .因此,它必须发出一个分支,以保护这种取消引用。d->flags
a != b
2赞
user16217248
11/13/2023
我怀疑这里的罪魁祸首是逻辑算子,它需要分支进行短路评估,而不是实际的比较算子。请尝试。&&
c |= (a == b) & (d->flags >> 4 & 1);
1赞
mirabilos
11/14/2023
@user16217248啊啊啊啊你是对的......并且编译器通常无法进行等价,因为短路(毕竟可能是零)......好点,因此它在 GCC 3.4.6 中也是无分支 ( + ) 的,即使对于 .d
cmp
sete
i386
评论
const T v[] = {b, a}; return v[a>b]