a =((b-c)< 0?0:(b-c))与a = b-c; a = a< 0? 0:a

时间:2014-05-17 11:41:10

标签: c assembly benchmarking avr avr-gcc

简单的问题:哪个更快

a = ((b - c) < 0 ? 0 : (b - c));

a = b - c;
a = a < 0 ? 0 : a;

我认为第二个选项更快,因为我们避免减去两次,但是在汇编级别哪个更快或者如何编译到汇编?

硬件是8位Atmel328

4 个答案:

答案 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位charint8_t整数类型会产生什么。我怀疑 2nd 形式可能会更好,因为根据C推广规则,所有中间算术和比较都将被评估为(signed) intIt 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

在AVR gcc 4.6上翻译为以下代码
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));

的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;

的c ++代码
#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

与以前完全相同。