Antonio
Antonio

Reputation: 651

How to optimize C++ avr code

To set or clear a bit in a register I use the following code:

template<int... pos, class Int>
static constexpr void write_one(Int& i)
{
    using expand = int[];
    expand{0,((i |= (Int{1} << pos)), 0)...};
}

template<int... pos, class Int>
static constexpr void write_zero(Int& i)
{
    using expand = int[];
    expand{0,((i &= ~(Int{1} << pos)), 0)...};
}

It works fine. To test its efficiency I write 2 test functions:

// The most efficiency
while(1){
    PORTB |= (1 << PB0);
    PORTB &= ~(1 << PB0);
}

// This is the one I want to measure
while(1){
     Bit::write_one<PB0>(PORTB);
     Bit::write_zero<PB0>(PORTB);
}

When I measure times with an oscilloscope the second one takes more time, so I disassamble the code getting the following:

; This is the first one (of course, the most efficient)
000000c8 <_Z12testv>:
ce: 28 9a           sbi 0x05, 0 ; 5
d0: 28 98           cbi 0x05, 0 ; 5
d2: fd cf           rjmp    .-6         ; 0xce <_Z12testv+0x6>

; The second one
000000c8 <_Z12testv>:
; The compiler optimize perfectly write_one<PB0>(PORTB)
ce: 28 9a           sbi 0x05, 0 ; 5

; but, look what happens with write_zero<PB0>(PORTB)!!! 
; Why the compiler can't write "cbi"???
; Here is the problem:
d0: 85 b1           in  r24, 0x05   ; 5
d2: 90 e0           ldi r25, 0x00   ; 0
d4: 8e 7f           andi    r24, 0xFE   ; 254
d6: 85 b9           out 0x05, r24   ; 5

d8: fa cf           rjmp    .-12        ; 0xce <_Z12testv+0x6>

I'm using avr-g++ 4.9.2 with -O3 flag.

Upvotes: 3

Views: 289

Answers (1)

Surt
Surt

Reputation: 16089

template<int... pos, class Int>
static constexpr void write_one(Int& i)
{
    using expand = int[];
    expand{0,((i |= (Int{1} << pos)), 0)...};
}
template<int... pos, class Int>
static constexpr void write_zero(Int& i)
{
    using expand = int[];
    expand{0,((i &= ~(Int{1} << pos)), 0)...};
}
I

Bit::write_one<PB0>(PORTB);
Bit::write_zero<PB0>(PORTB);

int[2] { 0, ((i |= (Int{1} << pos)), 0) }

int[2] { 0, 0 } // a tmp that is a nop
(i |= (Int{1} << pos))

(i |= (decl_type(PORTB){1} << int { PB0 }))

PORTB looks like it is an volatile uint5_t possible values 0-31 (AVR) with the value 0x5

(i |= ( uint5_t{1} << int {0}))

1 and 0 are literals/constexpr so it gives 1.

i |= 1

which code gens to

sbi 0x5, 0 // set port 5 bit 0


expand{0,((i &= ~(Int{1} << pos)), 0)...};

following the same logic gives

(i &= ~(1))

gives the code

d0: 85 b1           in  r24, 0x05   ; 5
d2: 90 e0           ldi r25, 0x00   ; 0 <---------- this value is not used nor set any flags???
d4: 8e 7f           andi    r24, 0xFE   ; 254 (i &= ~(1))
d6: 85 b9           out 0x05, r24   ; 5

So the conclusion must be that there is an error in the code gen for AVR as it generates a spurious instruction.

explanaion of ldi

Upvotes: 1

Related Questions