前几天,我和一个朋友就这两个片段发生了争执。哪个更快?为什么?
value = 5;
if (condition) {
value = 6;
}
和:
if (condition) {
value = 6;
} else {
value = 5;
}
如果value
是一个矩阵呢?
注意:我知道value = condition ? 6 : 5;
是存在的,我希望它会更快,但这不是一个选择。
编辑(由于问题暂时搁置,因此由员工请求):
发布于 2017-04-04 16:40:21
TL;DR:在未经优化的代码中,没有else
的if
似乎效率更高,但即使启用了最基本的优化级别,代码基本上也会重写为value = condition + 5
。
I gave it a try并为以下代码生成了程序集:
int ifonly(bool condition, int value)
{
value = 5;
if (condition) {
value = 6;
}
return value;
}
int ifelse(bool condition, int value)
{
if (condition) {
value = 6;
} else {
value = 5;
}
return value;
}
在禁用优化(-O0
)的gcc 6.3上,相应的区别是:
mov DWORD PTR [rbp-8], 5
cmp BYTE PTR [rbp-4], 0
je .L2
mov DWORD PTR [rbp-8], 6
.L2:
mov eax, DWORD PTR [rbp-8]
用于ifonly
,而ifelse
有
cmp BYTE PTR [rbp-4], 0
je .L5
mov DWORD PTR [rbp-8], 6
jmp .L6
.L5:
mov DWORD PTR [rbp-8], 5
.L6:
mov eax, DWORD PTR [rbp-8]
后者看起来效率稍低,因为它有一个额外的跳跃,但两者都有至少两个最多三个赋值,所以除非你真的需要压缩最后一滴性能(提示:除非你在航天飞机上工作,否则你不会这样做,即使那样你也可能不会),差异不会明显。
然而,即使使用最低优化级别(-O1
),这两个函数也会减少到相同的程度:
test dil, dil
setne al
movzx eax, al
add eax, 5
它基本上等同于
return 5 + condition;
假设condition
为0或1。更高的优化级别并不会真正改变输出,除非它们通过在开始时有效地将EAX
寄存器清零来避免movzx
。
免责声明:您可能不应该自己编写5 + condition
(即使标准保证将true
转换为整数类型会产生1
),因为阅读您的代码的人(可能包括您未来的自己)可能不会立即看出您的意图。这段代码的要点是显示编译器在这两种情况下产生的内容(实际上)是相同的。Ciprian Tomoiaga在评论中说得很好:
人类的工作是为人类编写代码,并让编译器为编写代码。< code >J233
发布于 2017-04-04 22:14:15
来自CompuChip的答案显示,对于int
,它们都针对相同的程序集进行了优化,所以这无关紧要。
如果
是一个矩阵呢?
我将以一种更一般的方式来解释这一点,例如,如果value
是一种构造和赋值都很昂贵(而移动成本很低)的类型,那该怎么办?
然后
T value = init1;
if (condition)
value = init2;
不是最优的,因为在condition
为真的情况下,您对init1
执行不必要的初始化,然后执行复制分配。
T value;
if (condition)
value = init2;
else
value = init3;
这个比较好。但是,如果默认构造昂贵,并且如果复制构造比初始化更昂贵,则仍然是次优的。
你有一个很好的条件运算符解:
T value = condition ? init1 : init2;
或者,如果您不喜欢条件运算符,可以创建一个辅助函数,如下所示:
T create(bool condition)
{
if (condition)
return {init1};
else
return {init2};
}
T value = create(condition);
根据init1
和init2
是什么,您还可以考虑以下内容:
auto final_init = condition ? init1 : init2;
T value = final_init;
但我必须再次强调,只有当构造和赋值对于给定类型来说非常昂贵时,这才是相关的。即便如此,只有通过对进行分析,您才能确定。
发布于 2017-04-04 23:05:02
在伪汇编语言中,
li #0, r0
test r1
beq L1
li #1, r0
L1:
可能快,也可能不快
test r1
beq L1
li #1, r0
bra L2
L1:
li #0, r0
L2:
这取决于实际CPU的复杂程度。从最简单到最花哨:
r0
无论如何只编写一次。这些CPU通常足以在指令获取器中处理无条件的分支,因此您不仅仅是在用写后写的代价来换取分支代价。我不知道还有没有人还在制造这种CPU。但是,使用乱序执行的“最著名的实现”的CPU可能会偷工减料地使用不太频繁使用的指令,因此您需要意识到这种情况可能会发生。一个真实的例子是false data dependencies on the destination registers in popcnt
and lzcnt
on Sandy Bridge CPUs.
如果分支是高度不可预测的,并且您的CPU具有条件设置或条件移动指令,则是时候使用它们了:
li #0,r0测试r1设置r0
或
li #0,测试li #1,r2 r0 r1 movne r2,r0
条件集版本也比任何其他替代方案更紧凑;如果该指令可用,即使分支是可预测的,实际上也可以保证它在这种情况下是正确的。条件移动版本需要额外的临时寄存器,并且总是浪费一条li
指令的分派和执行资源;如果分支实际上是可预测的,分支版本可能会更快。
https://stackoverflow.com/questions/43202012
复制相似问题