Ist es möglich, die Zeit zu finden, die Millis benötigt?

13

Die Funktion milliswürde in einer Zeitspanne von mehr als 100 Mikrosekunden oder weniger ausgeführt. Gibt es einen zuverlässigen Weg, um die Zeit zu messen, die ein einzelner Millis-Aufruf benötigt?

Ein Ansatz, der in den Sinn kommt micros, besteht darin, einen Aufruf von zu verwenden micros, der auch die vom Funktionsaufruf microsselbst benötigte Zeit einschließt. Je nachdem, wie lange Mikros dauern, kann die Messung für millisausgeschaltet sein.

Ich muss dies finden, da eine Anwendung, an der ich arbeite, genaue Zeitmessungen für jeden Schritt im Code erfordert, einschließlich millis.

Ascheshr
quelle
Können Sie klarstellen, was Sie hier fragen? Versuchen Sie, genaue Zeiten von millis () zu erhalten, oder versuchen Sie herauszufinden, wie lange es dauert, die Funktion millis () aufzurufen?
Cybergibbons
@ Cybergibbons Wie lange ein Anruf miilisdauert.
Asheeshr

Antworten:

21

Wenn Sie genau wissen möchten, wie lange etwas dauern wird, gibt es nur eine Lösung: Schauen Sie sich die Demontage an!

Beginnend mit dem Minimalcode:

void setup(){};

volatile uint16_t x;
void loop()
{
  x = millis();

}

Dieser Code, der kompiliert und dann eingegeben wird, avr-objdump -Serzeugt eine dokumentierte Demontage. Hier sind die interessanten Auszüge:

void loop() produziert:

000000a8 <loop>:
  a8:   0e 94 a7 00     call    0x14e   ; 0x14e <millis>
  ac:   60 93 00 01     sts 0x0100, r22
  b0:   70 93 01 01     sts 0x0101, r23
  b4:   80 93 02 01     sts 0x0102, r24
  b8:   90 93 03 01     sts 0x0103, r25
  bc:   08 95           ret

Was ist ein Funktionsaufruf ( call), vier Kopien (die jedes der Bytes im uint32_tRückgabewert von kopieren millis()(beachten Sie, dass die Arduino-Dokumente dies als ein bezeichnen long, aber sie sind falsch, um die Variablengrößen nicht explizit anzugeben)) und schließlich die Funktionsrückgabe.

callerfordert 4 Taktzyklen und jeweils sts2 Taktzyklen, sodass wir nur für den Funktionsaufruf-Overhead ein Minimum von 12 Taktzyklen haben.

Schauen wir uns nun die Demontage der <millis>Funktion an, die sich unter folgender Adresse befindet 0x14e:

unsigned long millis()
{
    unsigned long m;
    uint8_t oldSREG = SREG;
 14e:   8f b7           in  r24, 0x3f   ; 63

    // disable interrupts while we read timer0_millis or we might get an
    // inconsistent value (e.g. in the middle of a write to timer0_millis)
    cli();
 150:   f8 94           cli
    m = timer0_millis;
 152:   20 91 08 01     lds r18, 0x0108
 156:   30 91 09 01     lds r19, 0x0109
 15a:   40 91 0a 01     lds r20, 0x010A
 15e:   50 91 0b 01     lds r21, 0x010B
    SREG = oldSREG;
 162:   8f bf           out 0x3f, r24   ; 63

    return m;
}
 164:   b9 01           movw    r22, r18
 166:   ca 01           movw    r24, r20
 168:   08 95           ret

Wie Sie sehen, ist die millis()Funktion ziemlich einfach:

  1. in speichert die Einstellungen des Interrupt-Registers (1 Zyklus)
  2. cli schaltet die Interrupts aus (1 Zyklus)
  3. lds Kopiere eines der 4 Bytes des aktuellen Wertes des Millizählers in ein temporäres Register (2 Taktzyklen)
  4. lds Byte 2 (2 Taktzyklen)
  5. lds Byte 3 (2 Taktzyklen)
  6. lds Byte 4 (2 Taktzyklen)
  7. out Interrupt-Einstellungen wiederherstellen (1 Taktzyklus)
  8. movw Shuffle-Register um (1 Taktzyklus)
  9. movw und wieder (1 Taktzyklus)
  10. ret Rückkehr vom Unterprogramm (4 Zyklen)

Wenn wir also alle addieren, haben wir insgesamt 17 Taktzyklen in der millis()Funktion selbst plus einen Aufrufoverhead von 12 für insgesamt 29 Taktzyklen.

Unter der Annahme einer 16-MHz-Taktrate (die meisten Arduinos) beträgt jeder Taktzyklus 1 / 16e6Sekunden oder 0,0000000625 Sekunden, was 62,5 Nanosekunden entspricht. 62,5 ns * 29 = 1,812 Mikrosekunden.

Daher beträgt die Gesamtausführungszeit für einen einzelnen millis()Aufruf bei den meisten Arduinos 1,812 Mikrosekunden .


AVR Assembly Referenz

Als Randnotiz gibt es hier Raum für Optimierung! Wenn Sie die unsigned long millis(){}Funktionsdefinition so aktualisieren inline unsigned long millis(){}, dass sie aktuell ist , werden Sie den Anrufoverhead (auf Kosten einer geringfügig größeren Codegröße) entfernen . Außerdem sieht es so aus, als würde der Compiler zwei unnötige Schritte ausführen (die beiden movwAufrufe, aber ich habe es mir nicht so genau angesehen).

Wirklich, wenn man bedenkt, dass der Funktionsaufruf-Overhead 5 Anweisungen umfasst und der tatsächliche Inhalt der millis()Funktion nur 6 Anweisungen umfasst, denke ich, dass die millis()Funktion eigentlich inlinestandardmäßig sein sollte, aber die Arduino-Codebasis ist ziemlich schlecht optimiert.


Hier ist die vollständige Disassemby für alle Interessierten:

sketch_feb13a.cpp.elf:     file format elf32-avr


Disassembly of section .text:

00000000 <__vectors>:
    SREG = oldSREG;

    return m;
}

unsigned long micros() {
   0:   0c 94 34 00     jmp 0x68    ; 0x68 <__ctors_end>
   4:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
   8:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
   c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  10:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  14:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  18:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  1c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  20:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  24:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  28:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  2c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  30:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  34:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  38:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  3c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  40:   0c 94 5f 00     jmp 0xbe    ; 0xbe <__vector_16>
  44:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  48:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  4c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  50:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  54:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  58:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  5c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  60:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  64:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>

00000068 <__ctors_end>:
  68:   11 24           eor r1, r1
  6a:   1f be           out 0x3f, r1    ; 63
  6c:   cf ef           ldi r28, 0xFF   ; 255
  6e:   d8 e0           ldi r29, 0x08   ; 8
  70:   de bf           out 0x3e, r29   ; 62
  72:   cd bf           out 0x3d, r28   ; 61

00000074 <__do_copy_data>:
  74:   11 e0           ldi r17, 0x01   ; 1
  76:   a0 e0           ldi r26, 0x00   ; 0
  78:   b1 e0           ldi r27, 0x01   ; 1
  7a:   e2 e0           ldi r30, 0x02   ; 2
  7c:   f2 e0           ldi r31, 0x02   ; 2
  7e:   02 c0           rjmp    .+4         ; 0x84 <.do_copy_data_start>

00000080 <.do_copy_data_loop>:
  80:   05 90           lpm r0, Z+
  82:   0d 92           st  X+, r0

00000084 <.do_copy_data_start>:
  84:   a0 30           cpi r26, 0x00   ; 0
  86:   b1 07           cpc r27, r17
  88:   d9 f7           brne    .-10        ; 0x80 <.do_copy_data_loop>

0000008a <__do_clear_bss>:
  8a:   11 e0           ldi r17, 0x01   ; 1
  8c:   a0 e0           ldi r26, 0x00   ; 0
  8e:   b1 e0           ldi r27, 0x01   ; 1
  90:   01 c0           rjmp    .+2         ; 0x94 <.do_clear_bss_start>

00000092 <.do_clear_bss_loop>:
  92:   1d 92           st  X+, r1

00000094 <.do_clear_bss_start>:
  94:   ad 30           cpi r26, 0x0D   ; 13
  96:   b1 07           cpc r27, r17
  98:   e1 f7           brne    .-8         ; 0x92 <.do_clear_bss_loop>
  9a:   0e 94 f0 00     call    0x1e0   ; 0x1e0 <main>
  9e:   0c 94 ff 00     jmp 0x1fe   ; 0x1fe <_exit>

000000a2 <__bad_interrupt>:
  a2:   0c 94 00 00     jmp 0   ; 0x0 <__vectors>

000000a6 <setup>:
  a6:   08 95           ret

000000a8 <loop>:
  a8:   0e 94 a7 00     call    0x14e   ; 0x14e <millis>
  ac:   60 93 00 01     sts 0x0100, r22
  b0:   70 93 01 01     sts 0x0101, r23
  b4:   80 93 02 01     sts 0x0102, r24
  b8:   90 93 03 01     sts 0x0103, r25
  bc:   08 95           ret

000000be <__vector_16>:
#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ISR(TIM0_OVF_vect)
#else
ISR(TIMER0_OVF_vect)
#endif
{
  be:   1f 92           push    r1
  c0:   0f 92           push    r0
  c2:   0f b6           in  r0, 0x3f    ; 63
  c4:   0f 92           push    r0
  c6:   11 24           eor r1, r1
  c8:   2f 93           push    r18
  ca:   3f 93           push    r19
  cc:   8f 93           push    r24
  ce:   9f 93           push    r25
  d0:   af 93           push    r26
  d2:   bf 93           push    r27
    // copy these to local variables so they can be stored in registers
    // (volatile variables must be read from memory on every access)
    unsigned long m = timer0_millis;
  d4:   80 91 08 01     lds r24, 0x0108
  d8:   90 91 09 01     lds r25, 0x0109
  dc:   a0 91 0a 01     lds r26, 0x010A
  e0:   b0 91 0b 01     lds r27, 0x010B
    unsigned char f = timer0_fract;
  e4:   30 91 0c 01     lds r19, 0x010C

    m += MILLIS_INC;
  e8:   01 96           adiw    r24, 0x01   ; 1
  ea:   a1 1d           adc r26, r1
  ec:   b1 1d           adc r27, r1
    f += FRACT_INC;
  ee:   23 2f           mov r18, r19
  f0:   2d 5f           subi    r18, 0xFD   ; 253
    if (f >= FRACT_MAX) {
  f2:   2d 37           cpi r18, 0x7D   ; 125
  f4:   20 f0           brcs    .+8         ; 0xfe <__vector_16+0x40>
        f -= FRACT_MAX;
  f6:   2d 57           subi    r18, 0x7D   ; 125
        m += 1;
  f8:   01 96           adiw    r24, 0x01   ; 1
  fa:   a1 1d           adc r26, r1
  fc:   b1 1d           adc r27, r1
    }

    timer0_fract = f;
  fe:   20 93 0c 01     sts 0x010C, r18
    timer0_millis = m;
 102:   80 93 08 01     sts 0x0108, r24
 106:   90 93 09 01     sts 0x0109, r25
 10a:   a0 93 0a 01     sts 0x010A, r26
 10e:   b0 93 0b 01     sts 0x010B, r27
    timer0_overflow_count++;
 112:   80 91 04 01     lds r24, 0x0104
 116:   90 91 05 01     lds r25, 0x0105
 11a:   a0 91 06 01     lds r26, 0x0106
 11e:   b0 91 07 01     lds r27, 0x0107
 122:   01 96           adiw    r24, 0x01   ; 1
 124:   a1 1d           adc r26, r1
 126:   b1 1d           adc r27, r1
 128:   80 93 04 01     sts 0x0104, r24
 12c:   90 93 05 01     sts 0x0105, r25
 130:   a0 93 06 01     sts 0x0106, r26
 134:   b0 93 07 01     sts 0x0107, r27
}
 138:   bf 91           pop r27
 13a:   af 91           pop r26
 13c:   9f 91           pop r25
 13e:   8f 91           pop r24
 140:   3f 91           pop r19
 142:   2f 91           pop r18
 144:   0f 90           pop r0
 146:   0f be           out 0x3f, r0    ; 63
 148:   0f 90           pop r0
 14a:   1f 90           pop r1
 14c:   18 95           reti

0000014e <millis>:

unsigned long millis()
{
    unsigned long m;
    uint8_t oldSREG = SREG;
 14e:   8f b7           in  r24, 0x3f   ; 63

    // disable interrupts while we read timer0_millis or we might get an
    // inconsistent value (e.g. in the middle of a write to timer0_millis)
    cli();
 150:   f8 94           cli
    m = timer0_millis;
 152:   20 91 08 01     lds r18, 0x0108
 156:   30 91 09 01     lds r19, 0x0109
 15a:   40 91 0a 01     lds r20, 0x010A
 15e:   50 91 0b 01     lds r21, 0x010B
    SREG = oldSREG;
 162:   8f bf           out 0x3f, r24   ; 63

    return m;
}
 164:   b9 01           movw    r22, r18
 166:   ca 01           movw    r24, r20
 168:   08 95           ret

0000016a <init>:

void init()
{
    // this needs to be called before setup() or some functions won't
    // work there
    sei();
 16a:   78 94           sei

    // on the ATmega168, timer 0 is also used for fast hardware pwm
    // (using phase-correct PWM would mean that timer 0 overflowed half as often
    // resulting in different millis() behavior on the ATmega8 and ATmega168)
#if defined(TCCR0A) && defined(WGM01)
    sbi(TCCR0A, WGM01);
 16c:   84 b5           in  r24, 0x24   ; 36
 16e:   82 60           ori r24, 0x02   ; 2
 170:   84 bd           out 0x24, r24   ; 36
    sbi(TCCR0A, WGM00);
 172:   84 b5           in  r24, 0x24   ; 36
 174:   81 60           ori r24, 0x01   ; 1
 176:   84 bd           out 0x24, r24   ; 36
    // this combination is for the standard atmega8
    sbi(TCCR0, CS01);
    sbi(TCCR0, CS00);
#elif defined(TCCR0B) && defined(CS01) && defined(CS00)
    // this combination is for the standard 168/328/1280/2560
    sbi(TCCR0B, CS01);
 178:   85 b5           in  r24, 0x25   ; 37
 17a:   82 60           ori r24, 0x02   ; 2
 17c:   85 bd           out 0x25, r24   ; 37
    sbi(TCCR0B, CS00);
 17e:   85 b5           in  r24, 0x25   ; 37
 180:   81 60           ori r24, 0x01   ; 1
 182:   85 bd           out 0x25, r24   ; 37

    // enable timer 0 overflow interrupt
#if defined(TIMSK) && defined(TOIE0)
    sbi(TIMSK, TOIE0);
#elif defined(TIMSK0) && defined(TOIE0)
    sbi(TIMSK0, TOIE0);
 184:   ee e6           ldi r30, 0x6E   ; 110
 186:   f0 e0           ldi r31, 0x00   ; 0
 188:   80 81           ld  r24, Z
 18a:   81 60           ori r24, 0x01   ; 1
 18c:   80 83           st  Z, r24
    // this is better for motors as it ensures an even waveform
    // note, however, that fast pwm mode can achieve a frequency of up
    // 8 MHz (with a 16 MHz clock) at 50% duty cycle

#if defined(TCCR1B) && defined(CS11) && defined(CS10)
    TCCR1B = 0;
 18e:   e1 e8           ldi r30, 0x81   ; 129
 190:   f0 e0           ldi r31, 0x00   ; 0
 192:   10 82           st  Z, r1

    // set timer 1 prescale factor to 64
    sbi(TCCR1B, CS11);
 194:   80 81           ld  r24, Z
 196:   82 60           ori r24, 0x02   ; 2
 198:   80 83           st  Z, r24
#if F_CPU >= 8000000L
    sbi(TCCR1B, CS10);
 19a:   80 81           ld  r24, Z
 19c:   81 60           ori r24, 0x01   ; 1
 19e:   80 83           st  Z, r24
    sbi(TCCR1, CS10);
#endif
#endif
    // put timer 1 in 8-bit phase correct pwm mode
#if defined(TCCR1A) && defined(WGM10)
    sbi(TCCR1A, WGM10);
 1a0:   e0 e8           ldi r30, 0x80   ; 128
 1a2:   f0 e0           ldi r31, 0x00   ; 0
 1a4:   80 81           ld  r24, Z
 1a6:   81 60           ori r24, 0x01   ; 1
 1a8:   80 83           st  Z, r24

    // set timer 2 prescale factor to 64
#if defined(TCCR2) && defined(CS22)
    sbi(TCCR2, CS22);
#elif defined(TCCR2B) && defined(CS22)
    sbi(TCCR2B, CS22);
 1aa:   e1 eb           ldi r30, 0xB1   ; 177
 1ac:   f0 e0           ldi r31, 0x00   ; 0
 1ae:   80 81           ld  r24, Z
 1b0:   84 60           ori r24, 0x04   ; 4
 1b2:   80 83           st  Z, r24

    // configure timer 2 for phase correct pwm (8-bit)
#if defined(TCCR2) && defined(WGM20)
    sbi(TCCR2, WGM20);
#elif defined(TCCR2A) && defined(WGM20)
    sbi(TCCR2A, WGM20);
 1b4:   e0 eb           ldi r30, 0xB0   ; 176
 1b6:   f0 e0           ldi r31, 0x00   ; 0
 1b8:   80 81           ld  r24, Z
 1ba:   81 60           ori r24, 0x01   ; 1
 1bc:   80 83           st  Z, r24
#if defined(ADCSRA)
    // set a2d prescale factor to 128
    // 16 MHz / 128 = 125 KHz, inside the desired 50-200 KHz range.
    // XXX: this will not work properly for other clock speeds, and
    // this code should use F_CPU to determine the prescale factor.
    sbi(ADCSRA, ADPS2);
 1be:   ea e7           ldi r30, 0x7A   ; 122
 1c0:   f0 e0           ldi r31, 0x00   ; 0
 1c2:   80 81           ld  r24, Z
 1c4:   84 60           ori r24, 0x04   ; 4
 1c6:   80 83           st  Z, r24
    sbi(ADCSRA, ADPS1);
 1c8:   80 81           ld  r24, Z
 1ca:   82 60           ori r24, 0x02   ; 2
 1cc:   80 83           st  Z, r24
    sbi(ADCSRA, ADPS0);
 1ce:   80 81           ld  r24, Z
 1d0:   81 60           ori r24, 0x01   ; 1
 1d2:   80 83           st  Z, r24

    // enable a2d conversions
    sbi(ADCSRA, ADEN);
 1d4:   80 81           ld  r24, Z
 1d6:   80 68           ori r24, 0x80   ; 128
 1d8:   80 83           st  Z, r24
    // here so they can be used as normal digital i/o; they will be
    // reconnected in Serial.begin()
#if defined(UCSRB)
    UCSRB = 0;
#elif defined(UCSR0B)
    UCSR0B = 0;
 1da:   10 92 c1 00     sts 0x00C1, r1
#endif
}
 1de:   08 95           ret

000001e0 <main>:
#include <Arduino.h>

int main(void)
 1e0:   cf 93           push    r28
 1e2:   df 93           push    r29
{
    init();
 1e4:   0e 94 b5 00     call    0x16a   ; 0x16a <init>

#if defined(USBCON)
    USBDevice.attach();
#endif

    setup();
 1e8:   0e 94 53 00     call    0xa6    ; 0xa6 <setup>

    for (;;) {
        loop();
        if (serialEventRun) serialEventRun();
 1ec:   c0 e0           ldi r28, 0x00   ; 0
 1ee:   d0 e0           ldi r29, 0x00   ; 0
#endif

    setup();

    for (;;) {
        loop();
 1f0:   0e 94 54 00     call    0xa8    ; 0xa8 <loop>
        if (serialEventRun) serialEventRun();
 1f4:   20 97           sbiw    r28, 0x00   ; 0
 1f6:   e1 f3           breq    .-8         ; 0x1f0 <main+0x10>
 1f8:   0e 94 00 00     call    0   ; 0x0 <__vectors>
 1fc:   f9 cf           rjmp    .-14        ; 0x1f0 <main+0x10>

000001fe <_exit>:
 1fe:   f8 94           cli

00000200 <__stop_program>:
 200:   ff cf           rjmp    .-2         ; 0x200 <__stop_program>
Connor Wolf
quelle
Wow, tolle Antwort! +1
Der Kerl mit dem Hut
1) Die vier stsWerte sollten nicht als Verbindungsaufwand gezählt werden: Dies sind die Kosten für die Speicherung des Ergebnisses in einer flüchtigen Variablen, die Sie normalerweise nicht durchführen würden. 2) Auf meinem System (Arduino 1.0.5, gcc 4.8.2) habe ich das movws nicht. Dann betragen die millis()Gesprächskosten: 4 Overhead-Zyklen + 15 Zyklen an millis()sich = 19 Zyklen insgesamt (≈ 1,188 µs bei 16 MHz).
Edgar Bonet
1
@EdgarBonet - Das macht keinen Sinn, xist ein uint16_t. Es sollten höchstens 2 Exemplare sein, wenn dies die Ursache ist. Wie auch immer, die Frage ist, wie lange es dauert millis(), wenn es verwendet wird , und nicht, wenn es aufgerufen wird, während das Ergebnis ignoriert wird. Da bei jeder praktischen Verwendung etwas mit dem Ergebnis zu tun ist, habe ich das Speichern des Ergebnisses über erzwungen volatile. Normalerweise würde der gleiche Effekt durch die spätere Verwendung der Variablen erzielt, die auf den Rückgabewert des Anrufs gesetzt ist, aber ich wollte nicht, dass dieser zusätzliche Anruf Platz in der Antwort beansprucht.
Connor Wolf
Dies uint16_tin der Quelle stimmt nicht mit der Assembly überein (4 Bytes im RAM gespeichert). Sie haben wahrscheinlich die Quelle und die Zerlegung von zwei verschiedenen Versionen veröffentlicht.
Edgar Bonet
@ConnorWolf Erstaunliche Antwort und Erklärung. Vielen Dank!
Lefteris
8

Schreiben Sie eine Skizze, die 1000-mal millis, nicht durch Erstellen einer Schleife, sondern durch Kopieren und Einfügen. Messen Sie das und vergleichen Sie es mit der tatsächlich erwarteten Zeit. Beachten Sie, dass die Ergebnisse bei verschiedenen Versionen der IDE (und insbesondere des Compilers) variieren können.

Eine andere Möglichkeit besteht darin, einen E / A-Pin vor und nach dem Millis-Aufruf umzuschalten und dann die Zeit für einen sehr kleinen Wert und einen etwas größeren Wert zu messen. Vergleichen Sie die gemessenen Timings und berechnen Sie den Overhead.

Der genaueste Weg ist ein Blick auf die Disassemblierungsliste, den generierten Code. Aber das ist nichts für schwache Nerven. Sie müssen das Datenblatt sorgfältig studieren, wie lange jeder Unterrichtszyklus dauert.

jippie
quelle
Wie würden Sie die Zeit messen, die 1000 millis()Anrufe benötigen?
Apnorton
Wissen Sie, dass millis () von einem Interrupt auf timer0 geliefert wird, der bei jedem Tick eine interne Variable inkrementiert?
TheDoctor
@TheDoctor Ich habe mit verwechselt delay, du hast recht. Die Idee bleibt jedoch die gleiche, Sie können eine große Anzahl von Anrufen zeitlich festlegen und diese mitteln. Das globale Ausschalten der Interrupts ist jedoch möglicherweise keine gute Idee; o)
jippie
Vergewissern Sie sich, dass Ihr Datensatz groß genug ist, da das Drucken von Zeichen für die serielle Verbindung einige Millisekunden in Anspruch nimmt. Ich erinnere mich nicht an die genaue Zeit, aber ich denke, es sind ungefähr 0,6 ms pro Zeichen, die an Serial gesendet werden.
Steven10172
@ Steven10172 Du kannst eine leere Zeichenfolge mit einer 1000-fachen Zeichenfolge (oder mehr) abgleichen, dann kennst du das Delta und die Messung ist genauer.
jippie
3

Ich rufe Millis zum zweiten Mal auf und vergleiche dann die tatsächlichen mit den erwarteten.

Es wird einen minimalen Overhead geben, der jedoch an Bedeutung verliert, je öfter Sie millis () aufrufen.

Wenn du siehst

C:\Program Files (x86)\Arduino\Arduino ERW 1.0.5\hardware\arduino\cores\arduino\wiring.c

Sie können sehen, dass millis () bei nur 4 Anweisungen (cli is simply # define cli() \__asm__ \__volatile__ ("cli" ::))und einer Rückkehr sehr klein ist .

Ich würde es ungefähr 10 Millionen Mal nennen, wenn ich eine FOR-Schleife benutze, die eine volatile als Bedingung hat. Das Schlüsselwort volatile verhindert, dass der Compiler eine Optimierung der Schleife selbst versucht.

Ich garantiere nicht, dass das Folgende syntaktisch perfekt ist.

int temp1,temp2;
temp1=millis();
for (volatile unsigned int j=0;j<1000000;++j){
temp2=millis();}
Serial.print("Execution time = ");
Serial.print((temp2-temp1,DEC);
Serial.print("ms");

Ich vermute, das dauert ~ 900ms oder ungefähr 56us pro Anruf nach Millis. (Ich habe keinen Aruduino-Geldautomaten.

80HD
quelle
1
Sie sollten zu ändern, int temp1,temp2;um volatile int temp1,temp2;zu verhindern, dass der Compiler sie möglicherweise entfernt optimiert.
Connor Wolf
Guter Anruf auf dem flüchtigen. Ich wollte das definitiv reinlegen und tat es dann nicht. Ich hätte auch erwähnen sollen, dass der Weg zu einem angemesseneren Benchmark darin besteht, eine leere Schleife auszuführen, diese Ausführungszeit aufzuzeichnen und die Schleife dann während der Arbeit erneut auszuführen. Subtrahieren Sie die Differenz, dividieren Sie sie durch die Anzahl der Iterationen, und Sie erhalten eine äußerst genaue Ausführungszeit.
15.
Diese Art von Benchmark funktioniert nur auf einem System, das Ihre Codeausführung niemals vorwegnimmt. Die Arduino-Umgebung verfügt standardmäßig über regelmäßige Interrupts, die regelmäßig ausgeführt werden. Eine bessere Lösung wäre, bei jeder Ausführung einen Pin umzuschalten und mit einem hochauflösenden Zeitgeber die Umschaltrate zu messen, wenn der betreffende Code ausgeführt wird und nicht, und die minimale Ausführungszeit für jede Ausführung über eine Reihe von Beispielen zu berechnen Subtrahieren Sie die Basislinie und behandeln Sie diese als Ihre Ausführungszeit. Angenommen, Ihre Ausführungszeit ist kürzer als die minimale Zeit zwischen Interrupts.
Connor Wolf