简单的问题:哪个更快
a = ((b - c) < 0 ? 0 : (b - c));
或
a = b - c;
a = a < 0 ? 0 : a;
我认为第二个选项更快,因为我们避免减去两次,但是在汇编级别哪个更快或者如何编译到汇编?
硬件是8位Atmel328
答案 0 :(得分:5)
编译器非常擅长此类表达式评估。我希望在使用-O2
或-Os
进行编译时没有区别 - 优化大小,这在AVR中很常见。
由于这种表达肯定会在更复杂的功能的背景下进行评估,因此不可能证明哪种表达更好?&#39;在所有情况下,但对于avr-gcc
(4.8.2),编译:
#if (1)
int eval (int b, int c)
{
return ((b - c) < 0 ? 0 : (b - c));
}
#else
int eval (int b, int c)
{
int a = b - c;
return (a < 0 ? 0 : a);
}
#endif
对于两种情况,使用-O2
生成相同的代码:
avr-gcc -mmcu=atmega328 -c -O2 -S avrtest.c
eval:
.L__stack_usage = 0
sub r24,r22
sbc r25,r23
sbrs r25,7
ret
.L4:
ldi r24,0
ldi r25,0
ret
两种情况下使用-Os
的代码相同:
eval:
.L__stack_usage = 0
sub r24,r22
sbc r25,r23
sbrs r25,7
rjmp .L2
ldi r24,0
ldi r25,0
.L2:
ret
如果寄存器(r25)中的位(7)置位,则 sbrs
跳过下一条指令 - 也就是说,int
的msb(由[hi = r25: lo = r24]
表示 - 设置 - 表示负数。
如果(b - c) < 0
,则都需要(6)指令。否则,-O2
分别需要(4)或(5)指令。两个版本都具有相同的指令数。
avr-gcc
的最佳建议是始终确保-std=c99
已启用(默认情况下 - avr-libc
标头IIRC必需),并使用-Os
优化级别。
注意:如果使用unsigned int
,它将改变语义,因为:(b - c) >= 0
,在2的补码算术中,因此:a = b - c >= 0
;即,LHS永远不会错误。
我们还可以查看8位char
或int8_t
整数类型会产生什么。我怀疑 2nd 形式可能会更好,因为根据C推广规则,所有中间算术和比较都将被评估为(signed) int
。 It complicates things...
答案 1 :(得分:4)
知道哪个更快的唯一方法是在实际设置中执行两个版本并计时。编译器可能会为两个版本生成相同的代码。但是如果不通过编译器推送代码,你就无法知道。
答案 2 :(得分:1)
这可以更优雅地表达为最大操作:
a = max(b - c, 0);
任何体面的编译器都应为此生成相当优化的代码。
答案 3 :(得分:1)
更新
正如彼得·科德斯在评论中指出的那样,马特·戈德伯特在great site做了一个可以探索的地方。根据编译器资源管理器站点,以下代码
int eval_redundant (int b, int c)
{
return ((b - c) < 0 ? 0 : (b - c));
}
int eval_ternary (int b, int c)
{
int a = b - c;
return (a < 0 ? 0 : a);
}
int eval_if (int b, int c)
{
int a;
if (b > c) // AVR gcc4.6 doesn't CSE with the subtraction :/
a = b - c;
else
a = 0;
return a;
}
使用-Os
eval_redundant(int, int):
sub r24,r22
sbc r25,r23
sbrs r25,7
rjmp .L2
ldi r24,lo8(0)
ldi r25,hi8(0)
.L2:
ret
eval_ternary(int, int):
sub r24,r22
sbc r25,r23
sbrs r25,7
rjmp .L4
ldi r24,lo8(0)
ldi r25,hi8(0)
.L4:
ret
eval_if(int, int):
cp r22,r24
cpc r23,r25
brge .L7
sub r24,r22
sbc r25,r23
ret
.L7:
ldi r24,lo8(0)
ldi r25,hi8(0)
ret
正如您所看到的,旧的AVR gcc编译器并没有使用减法操作来消除比较,但这只发生在显式if
语句的情况下。
使用ARM64 gcc 8.2和x86-64 clang 7.0进行编译都会从所有三个版本生成相同的代码。
以前我发现了这个:
a = ((b - c) < 0 ? 0 : (b - c));
#line 1 "a.ino"
#include "Arduino.h"
void setup();
void loop();
#line 1
void setup() {
int a = 0;
int b = analogRead(1); // this is only here, so the compiler doesn't remove the whole experiment
int c = analogRead(2); // same as before
a = ((b - c) < 0 ? 0 : (b - c));
digitalWrite(0, a); // same as before
}
void loop() {
}
编译为
00000102 <setup>:
102: 0f 93 push r16
104: 1f 93 push r17
106: 81 e0 ldi r24, 0x01 ; 1
108: 0e 94 97 00 call 0x12e ; 0x12e <analogRead>
10c: 8c 01 movw r16, r24
10e: 82 e0 ldi r24, 0x02 ; 2
110: 0e 94 97 00 call 0x12e ; 0x12e <analogRead>
114: b8 01 movw r22, r16
116: 68 1b sub r22, r24
118: 79 0b sbc r23, r25
11a: 77 ff sbrs r23, 7
11c: 02 c0 rjmp .+4 ; 0x122 <setup+0x20>
11e: 60 e0 ldi r22, 0x00 ; 0
120: 70 e0 ldi r23, 0x00 ; 0
122: 80 e0 ldi r24, 0x00 ; 0
124: 0e 94 b9 00 call 0x172 ; 0x172 <digitalWrite>
128: 1f 91 pop r17
12a: 0f 91 pop r16
12c: 08 95 ret
和a = b - c; a = a < 0 ? 0 : a;
#line 1 "a.ino"
#include "Arduino.h"
void setup();
void loop();
#line 1
void setup() {
int a = 0;
int b = analogRead(1);
int c = analogRead(2);
a = b - c;
a = a < 0 ? 0 : a;
digitalWrite(0, a);
}
void loop() {
}
编译为
00000102 <setup>:
102: 0f 93 push r16
104: 1f 93 push r17
106: 81 e0 ldi r24, 0x01 ; 1
108: 0e 94 97 00 call 0x12e ; 0x12e <analogRead>
10c: 8c 01 movw r16, r24
10e: 82 e0 ldi r24, 0x02 ; 2
110: 0e 94 97 00 call 0x12e ; 0x12e <analogRead>
114: b8 01 movw r22, r16
116: 68 1b sub r22, r24
118: 79 0b sbc r23, r25
11a: 77 ff sbrs r23, 7
11c: 02 c0 rjmp .+4 ; 0x122 <setup+0x20>
11e: 60 e0 ldi r22, 0x00 ; 0
120: 70 e0 ldi r23, 0x00 ; 0
122: 80 e0 ldi r24, 0x00 ; 0
124: 0e 94 b9 00 call 0x172 ; 0x172 <digitalWrite>
128: 1f 91 pop r17
12a: 0f 91 pop r16
12c: 08 95 ret
与以前完全相同。