Jul*_*n__ 80 c++ performance assembly microbenchmark c++11
我前几天和朋友争论过这两个片段.哪个更快,为什么?
value = 5;
if (condition) {
value = 6;
}
Run Code Online (Sandbox Code Playgroud)
和:
if (condition) {
value = 6;
} else {
value = 5;
}
Run Code Online (Sandbox Code Playgroud)
如果value
是矩阵怎么办?
注意:我知道value = condition ? 6 : 5;
存在并且我希望它更快,但它不是一个选项.
编辑(工作人员要求,因为问题暂时搁置):
Com*_*hip 274
TL; DR:在if
未经优化的代码中,else
似乎没有更高效,但即使最基本的优化级别启用,代码也基本上被重写为value = condition + 5
.
我试了一下,为以下代码生成了程序集:
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;
}
Run Code Online (Sandbox Code Playgroud)
在gcc 6.3上,禁用了优化(-O0
),相关的区别是:
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]
Run Code Online (Sandbox Code Playgroud)
因为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]
Run Code Online (Sandbox Code Playgroud)
后者看起来效率稍差,因为它有一个额外的跳跃,但两者至少有两个,最多三个任务,所以除非你真的需要挤压每一滴性能(暗示:除非你正在航天飞机上工作,否则你没有,即便如此,你可能不这样做.差异不会明显.
但是,即使使用最低优化级别(-O1
),两个函数都会减少到相同:
test dil, dil
setne al
movzx eax, al
add eax, 5
Run Code Online (Sandbox Code Playgroud)
这基本上相当于
return 5 + condition;
Run Code Online (Sandbox Code Playgroud)
假设condition
为零或一.较高的优化级别并不会真正改变输出,除非它们设法避免在开始时movzx
有效地将EAX
寄存器置零.
免责声明:您可能不应该5 + condition
自己编写(即使标准保证转换true
为整数类型1
),因为您的意图可能不会立即显示给阅读代码的人(可能包括您未来的自我).这段代码的要点是表明编译器在两种情况下产生的内容(实际上)是相同的.Ciprian Tomoiaga在评论中说得很好:
一个人的工作就是写代码的人,让编译器用于编写代码的机器.
bol*_*lov 44
CompuChip的答案表明,因为int
它们都针对同一个组件进行了优化,所以无关紧要.
如果值是矩阵怎么办?
我将以更一般的方式解释这一点,即如果value
是一种类型,其构造和分配是昂贵的(并且移动是便宜的).
然后
T value = init1;
if (condition)
value = init2;
Run Code Online (Sandbox Code Playgroud)
是次优的,因为在情况condition
属实的情况下,您执行不必要的初始化init1
,然后执行复制分配.
T value;
if (condition)
value = init2;
else
value = init3;
Run Code Online (Sandbox Code Playgroud)
这个更好.但如果默认构造很昂贵,并且复制构造比初始化更昂贵,那么仍然是次优的.
你有条件运算符解决方案是好的:
T value = condition ? init1 : init2;
Run Code Online (Sandbox Code Playgroud)
或者,如果您不喜欢条件运算符,则可以创建一个这样的辅助函数:
T create(bool condition)
{
if (condition)
return {init1};
else
return {init2};
}
T value = create(condition);
Run Code Online (Sandbox Code Playgroud)
取决于你是什么init1
,init2
你也可以考虑这个:
auto final_init = condition ? init1 : init2;
T value = final_init;
Run Code Online (Sandbox Code Playgroud)
但是,我必须再次强调,只有当给定类型的构造和分配真的很昂贵时,这才是相关的.即便如此,只有通过剖析你才能确定.
zwo*_*wol 11
在伪汇编语言中,
li #0, r0
test r1
beq L1
li #1, r0
L1:
Run Code Online (Sandbox Code Playgroud)
可能比也可能不快
test r1
beq L1
li #1, r0
bra L2
L1:
li #0, r0
L2:
Run Code Online (Sandbox Code Playgroud)
取决于实际CPU的复杂程度.从最简单到最高兴:
对于大约1990年后制造的任何 CPU,良好的性能取决于指令缓存中的代码拟合.因此,如果有疑问,请尽量减少代码大小.这有利于第一个例子.
有了一个基本的" 有序,五级流水线 "CPU,这仍然是你在许多微控制器中得到的CPU,每次采用分支条件或无条件时都会出现管道泡沫,因此最小化也很重要分支指令的数量.这也有利于第一个例子.
一些更复杂的CPU - 足够用来执行" 无序执行 ",但不足以使用该概念的最着名的实现 - 可能在遇到写入后写入危险时引起管道泡沫.这有利于第二个例子,r0
无论如何只写一次.这些CPU通常是足够看上处理在取指令无条件分支,所以你不只是交易的写后读处罚分支处罚.
我不知道是否有人还在制造这种CPU.然而,CPU的不使用乱序执行的"最有名的实现"有可能削减对不太常用的指令角落,所以你必须要知道,这样的事情可能发生.一个真实的例子是Sandy Bridge CPU 中popcnt
和lzcnt
上的目标寄存器的错误数据依赖性.
在最高端,OOO引擎将最终为两个代码片段发出完全相同的内部操作序列 - 这是"不用担心,编译器将以任何方式生成相同的机器代码"的硬件版本.但是,代码大小仍然很重要,现在您还应该担心条件分支的可预测性. 分支预测失败可能导致完整的管道冲洗,这对性能来说是灾难性的; 请参阅为什么处理排序数组比未排序数组更快?了解这可以带来多大的不同.
如果分支是非常不可预测的,你的CPU具有条件设定或有条件移动指令,这是使用它们的时间:
li #0, r0
test r1
setne r0
Run Code Online (Sandbox Code Playgroud)
要么
li #0, r0
li #1, r2
test r1
movne r2, r0
Run Code Online (Sandbox Code Playgroud)
条件集版本也比任何其他替代版本更紧凑; 如果该指令可用,则实际上保证对于这种情况是正确的事情,即使该分支是可预测的.条件移动版本需要一个额外的临时寄存器,并且总是浪费一条li
指令的调度和执行资源; 如果分支实际上是可预测的,那么分支版本可能会更快.
在未经优化的代码中,第一个示例总是分配一次变量,有时两次.第二个例子只分配一次变量.两个代码路径上的条件相同,因此无关紧要.在优化代码中,它取决于编译器.
与往常一样,如果您有关,请生成程序集并查看编译器实际执行的操作.
什么会让你认为他们中的任何一个甚至一个班轮更快或更慢?
unsigned int fun0 ( unsigned int condition, unsigned int value )
{
value = 5;
if (condition) {
value = 6;
}
return(value);
}
unsigned int fun1 ( unsigned int condition, unsigned int value )
{
if (condition) {
value = 6;
} else {
value = 5;
}
return(value);
}
unsigned int fun2 ( unsigned int condition, unsigned int value )
{
value = condition ? 6 : 5;
return(value);
}
Run Code Online (Sandbox Code Playgroud)
高级语言的更多代码行使编译器能够使用更多代码,因此如果您想对其进行一般规则,请为编译器提供更多代码.如果算法与上面的情况相同,那么人们会期望编译器具有最小的优化来计算出来.
00000000 <fun0>:
0: e3500000 cmp r0, #0
4: 03a00005 moveq r0, #5
8: 13a00006 movne r0, #6
c: e12fff1e bx lr
00000010 <fun1>:
10: e3500000 cmp r0, #0
14: 13a00006 movne r0, #6
18: 03a00005 moveq r0, #5
1c: e12fff1e bx lr
00000020 <fun2>:
20: e3500000 cmp r0, #0
24: 13a00006 movne r0, #6
28: 03a00005 moveq r0, #5
2c: e12fff1e bx lr
Run Code Online (Sandbox Code Playgroud)
并不是一个惊喜,它以不同的顺序执行第一个功能,但执行时间相同.
0000000000000000 <fun0>:
0: 7100001f cmp w0, #0x0
4: 1a9f07e0 cset w0, ne
8: 11001400 add w0, w0, #0x5
c: d65f03c0 ret
0000000000000010 <fun1>:
10: 7100001f cmp w0, #0x0
14: 1a9f07e0 cset w0, ne
18: 11001400 add w0, w0, #0x5
1c: d65f03c0 ret
0000000000000020 <fun2>:
20: 7100001f cmp w0, #0x0
24: 1a9f07e0 cset w0, ne
28: 11001400 add w0, w0, #0x5
2c: d65f03c0 ret
Run Code Online (Sandbox Code Playgroud)
希望你能得到这个想法,如果不明显的是不同的实现实际上没有不同,你可以尝试这个.
就矩阵而言,不确定这是多么重要,
if(condition)
{
big blob of code a
}
else
{
big blob of code b
}
Run Code Online (Sandbox Code Playgroud)
只是将相同的if-then-else包装器放在大块代码周围,它们值= 5或更复杂的东西.同样地,比较即使它是一大块代码,它仍然必须被计算,并且等于或不等于某些东西通常用负数编译,如果(条件)做某事通常被编译为好像不是条件goto.
00000000 <fun0>:
0: 0f 93 tst r15
2: 03 24 jz $+8 ;abs 0xa
4: 3f 40 06 00 mov #6, r15 ;#0x0006
8: 30 41 ret
a: 3f 40 05 00 mov #5, r15 ;#0x0005
e: 30 41 ret
00000010 <fun1>:
10: 0f 93 tst r15
12: 03 20 jnz $+8 ;abs 0x1a
14: 3f 40 05 00 mov #5, r15 ;#0x0005
18: 30 41 ret
1a: 3f 40 06 00 mov #6, r15 ;#0x0006
1e: 30 41 ret
00000020 <fun2>:
20: 0f 93 tst r15
22: 03 20 jnz $+8 ;abs 0x2a
24: 3f 40 05 00 mov #5, r15 ;#0x0005
28: 30 41 ret
2a: 3f 40 06 00 mov #6, r15 ;#0x0006
2e: 30 41
Run Code Online (Sandbox Code Playgroud)
我们刚刚和其他人一起在stackoverflow上进行了这个练习.这个mips编译器很有趣,在这种情况下不仅实现了功能相同,而且只有一个函数只是跳转到另一个以节省代码空间.虽然不是这样做的
00000000 <fun0>:
0: 0004102b sltu $2,$0,$4
4: 03e00008 jr $31
8: 24420005 addiu $2,$2,5
0000000c <fun1>:
c: 0004102b sltu $2,$0,$4
10: 03e00008 jr $31
14: 24420005 addiu $2,$2,5
00000018 <fun2>:
18: 0004102b sltu $2,$0,$4
1c: 03e00008 jr $31
20: 24420005 addiu $2,$2,5
Run Code Online (Sandbox Code Playgroud)
更多目标.
00000000 <_fun0>:
0: 1166 mov r5, -(sp)
2: 1185 mov sp, r5
4: 0bf5 0004 tst 4(r5)
8: 0304 beq 12 <_fun0+0x12>
a: 15c0 0006 mov $6, r0
e: 1585 mov (sp)+, r5
10: 0087 rts pc
12: 15c0 0005 mov $5, r0
16: 1585 mov (sp)+, r5
18: 0087 rts pc
0000001a <_fun1>:
1a: 1166 mov r5, -(sp)
1c: 1185 mov sp, r5
1e: 0bf5 0004 tst 4(r5)
22: 0204 bne 2c <_fun1+0x12>
24: 15c0 0005 mov $5, r0
28: 1585 mov (sp)+, r5
2a: 0087 rts pc
2c: 15c0 0006 mov $6, r0
30: 1585 mov (sp)+, r5
32: 0087 rts pc
00000034 <_fun2>:
34: 1166 mov r5, -(sp)
36: 1185 mov sp, r5
38: 0bf5 0004 tst 4(r5)
3c: 0204 bne 46 <_fun2+0x12>
3e: 15c0 0005 mov $5, r0
42: 1585 mov (sp)+, r5
44: 0087 rts pc
46: 15c0 0006 mov $6, r0
4a: 1585 mov (sp)+, r5
4c: 0087 rts pc
00000000 <fun0>:
0: 00a03533 snez x10,x10
4: 0515 addi x10,x10,5
6: 8082 ret
00000008 <fun1>:
8: 00a03533 snez x10,x10
c: 0515 addi x10,x10,5
e: 8082 ret
00000010 <fun2>:
10: 00a03533 snez x10,x10
14: 0515 addi x10,x10,5
16: 8082 ret
Run Code Online (Sandbox Code Playgroud)
和编译器
使用此i代码,可以预期不同的目标也匹配
define i32 @fun0(i32 %condition, i32 %value) #0 {
%1 = icmp ne i32 %condition, 0
%. = select i1 %1, i32 6, i32 5
ret i32 %.
}
; Function Attrs: norecurse nounwind readnone
define i32 @fun1(i32 %condition, i32 %value) #0 {
%1 = icmp eq i32 %condition, 0
%. = select i1 %1, i32 5, i32 6
ret i32 %.
}
; Function Attrs: norecurse nounwind readnone
define i32 @fun2(i32 %condition, i32 %value) #0 {
%1 = icmp ne i32 %condition, 0
%2 = select i1 %1, i32 6, i32 5
ret i32 %2
}
00000000 <fun0>:
0: e3a01005 mov r1, #5
4: e3500000 cmp r0, #0
8: 13a01006 movne r1, #6
c: e1a00001 mov r0, r1
10: e12fff1e bx lr
00000014 <fun1>:
14: e3a01006 mov r1, #6
18: e3500000 cmp r0, #0
1c: 03a01005 moveq r1, #5
20: e1a00001 mov r0, r1
24: e12fff1e bx lr
00000028 <fun2>:
28: e3a01005 mov r1, #5
2c: e3500000 cmp r0, #0
30: 13a01006 movne r1, #6
34: e1a00001 mov r0, r1
38: e12fff1e bx lr
fun0:
push.w r4
mov.w r1, r4
mov.w r15, r12
mov.w #6, r15
cmp.w #0, r12
jne .LBB0_2
mov.w #5, r15
.LBB0_2:
pop.w r4
ret
fun1:
push.w r4
mov.w r1, r4
mov.w r15, r12
mov.w #5, r15
cmp.w #0, r12
jeq .LBB1_2
mov.w #6, r15
.LBB1_2:
pop.w r4
ret
fun2:
push.w r4
mov.w r1, r4
mov.w r15, r12
mov.w #6, r15
cmp.w #0, r12
jne .LBB2_2
mov.w #5, r15
.LBB2_2:
pop.w r4
ret
Run Code Online (Sandbox Code Playgroud)
从技术上讲,这些解决方案中有一些性能存在差异,有时结果是5个案例跳过结果是6个代码,反之亦然,是一个比执行更快的分支?人们可以争辩,但执行应该有所不同.但是这更像是一个if条件vs如果没有条件在代码中导致编译器执行如果这跳过else执行通过.但这不一定是由于编码风格,而是由于比较以及if和else的情况在任何语法中.
归档时间: |
|
查看次数: |
13087 次 |
最近记录: |