Arduino Tidsforhold Sprogstrukturer

Fra Holstebro HTX Wiki
Skift til: navigering, søgning

Denne wikiside omhandler tidsforhold i C henvendt til Arduino. Tidsforholdene handler om hvor mange maskinoperationer, og dermed hvor lang, der skal til for at eksekvere en bestemt sprogstruktur (if, for, while, ect.).

Overblik

Der er udført nogle tests på de forskellige sprogstrukturer. Her er et overblik over resultaterne. Man kan gå ned i de enkelte afsnit, for at få resultaterne og fremgangsmåden uddybet.

For-loop resultater (tallelene er antal maskininstruktioner) :

for første loop midterste loops sidste loop total for 10 iterationer
byte 4 3 2 30
int 6 4 3 41
long 8 6 5 61

if-else resultater (tallelene er antal maskininstruktioner):

true false
if 3 4
if-else 5 4
if-else if 5 7

Målingsramme

De forskellige sprogstrukturer er blevet målt internt i koden, og for at få den mest præcise værdi er alle sprogstrukturer blevet kørt igennem 10000 gange, og det gennemsnitlige tidsforhold er fundet. Problemet ved dette er dog at det også tager tid for Arduinoen at køre selve loopet. Det betyder at vi altså er nødt til at finde ud af hvordan lang tid det tager et for-loop at blive kørt igennem 10000 gange, så vi kan trække denne værdi fra, når vi måler på de sprogstrukturer, der er inde i for-loopet.

Dog er der et problem med dette. Hvis man har et tomt for-loop, fjerner compileren loopet ved compiling af koden. Man deklarerer derfor for-loopets variabel som volatile, hvilket sikrer at koden udfører alle operationer på denne variabel. Det betyder at compileren ikke kan lave reducering eller optimering på koden. [1]. Der er derfor udviklet følgende målingsramme. Jo længere tid programmet kører, jo mere tilnærmer man sig den præcis værdi:

Koden kan downloades fra denne ZIP-fil.

void setup() {
  Serial.begin(115200);
}

unsigned long ulmicros;
unsigned long ultime;

float total;

unsigned long iterations;

volatile int test;

void loop() {
  ulmicros = micros();
  
  for(volatile int i = 0; i < 10000; i++){

    //testen foregår her
    
  }
  
  ultime = micros() - ulmicros;

  total += ultime - 11951.50;

  iterations++;
  
  Serial.print("Gennemsnitlig værdi: ");
  Serial.print((total/iterations)/10);
  Serial.println("ns");
  delay(5);
}

OBS. Husk at læse serieloutputtet med standarden 115200.

Der er her sat et delay på på 5 milisekunder, da man gerne vil undgå at ikke-afviklet serieloutput påvirker kodens runtime, og gør koden langsommere. Kort sagt: på denne måde sikrer man sig at koden, der bliver eksikveret, i det målte tidsrum, kun er for-loopet. Samtidigt er serielkommunikationen sat til 115200, da serieloutputtet gerne skulle afvikles hurtigst muligt. Se mere i Arduino Serielle Tidsforhold.

Test på en original Arduino Uno R3 giver at 10000 iterationer af for-loopet tager 11951,50 mikrosekunder. (Dette kan variere ved forskellige boards) Derfor trækker vi denne værdi fra, når vi skal måle en værdi, så vi kun får den tid det tager at køre det, som reelt er testen. Dette bliver grundlag for resten af testene på denne wiki-side.

Læg også mærke til at det tal man får ud er i enheden nanosekunder, og det beskriver den tid, det gennemsnitligt tager at køre testen en gang. Grunden til at tallet vi har bliver divideret med 10, er at værdien først skal divideres med 10000, for at få den gennemsnitlige tid på en iteration, hvorefter den skal ganges med 1000 for at få tallet fra mikrosekunder til nanosekunder.

for

Her er et eksempel på hvordan for-loopet er blevet testet. Her bliver byte-typen testet:

Koden kan downloades fra denne ZIP-fil.

void setup() {
  Serial.begin(115200);
}

unsigned long ulmicros;
unsigned long ultime;

float total;

unsigned long iterations;

volatile int test;

volatile byte b;

void loop() {
  ulmicros = micros();
  
  for(volatile int i = 0; i < 10000; i++){

    for(byte j = 0; j < 10; j++){
      PORTC = B10000000;
    }
    
  }
  
  ultime = micros() - ulmicros;

  total += ultime - 11951.50;

  iterations++;
  
  Serial.print("Gennemsnitlig værdi: ");
  Serial.print((total/iterations)/10);
  Serial.println("ns");
  delay(5);
}

Typerne byte, int og long er blevet testet i for-loopet, da disse er de eneste typer man realistisk set ville bruge i et for-loop. Samtidigt indgår linjen: "PORTC = B10000000;" blot for at undgå at for-loopet blev optimeret væk af compileren. Denne linje tager lige præcis 1 maskininstruktion (62,5ns med en 16MHz processor), og kan derfor beregnes væk efter testen.

Der er forskel på for-loopets første, sidste og alle midterste iterationer, og det er derfor ønsket at teste hvor mange maskinoperationer de forskellige tager. Dette er testet ved at se på forskellen i tiden det tager for at køre 10 og 11 iterationer af for-loopet, for at finde ud af hvor lang tid en midter-iteration tager. Den første iteration er derefter blevet beregnet ved, at tage antal maskinoperationer det tager at definere typen (ex. byte b = 0), og lægge det sammen med hvad en midter-iteration tager. Disse værdier findes på wiki-siden Arduino Tidsforhold Variabeltyper. Til sidst er det sidste loops antal maskininstruktioner fundet ved at trække startiterationen og alle de midterste iterationer fra den totale tid.

Disse værdier er skrevet ind i følgende tabel:

for første loop midterste loops sidste loop total for 10 iterationer
byte 4 3 2 30
int 6 4 3 41
long 8 6 5 61

while

While-loop blev målt på samme måde som de andre sprogstrukturer. Ved at køre det igennem en for-loop 10000 gange, og registrere tidsforskellen mellem før og efter for-loopen.

void loop() {
  ulmicros = micros();
  for(int i = 0; i < 1; i++){
    control = 0;
    j = 0;
    while (j < 0){
      j++;
    }
  }
  ultime = micros() - ulmicros;



Tabel over antal instruktioner det tager, at køre forskellige variabler igennem en while-loop, og tælle op til forskellige tal.

Variabeltype while < 0 while < 1 while < 10 while < 100
Byte 2 16 115 1105
Char 5 16 115 1105
Word 4 25 198 1908
Int 7 27 198 1908
Long 11 49 364 3514
Float 60 236 1858 20305

do while

Denne afsnit er ikke lavet endnu.

switch case

Denne afsnit er ikke lavet endnu.

if else

Her er et eksempel på hvordan testene er forløbet. Her bliver if-else if strukturen testet.

Koden kan downloades fra denne ZIP-fil.

void setup() {
  Serial.begin(115200);
}

unsigned long ulmicros;
unsigned long ultime;

float total;

unsigned long iterations;

volatile int test;

volatile byte b;

void loop() {
  ulmicros = micros();
  
  for(volatile int i = 0; i < 10000; i++){

    if(i > 10002){
      PORTC = B10000000;
    }else if(i < 10001){
      PORTB = B10000000;
    }
    
  }
  
  ultime = micros() - ulmicros;

  total += ultime - 11951.50;

  iterations++;
  Serial.print("Gennemsnitlig værdi: ");
  Serial.print((total/iterations)/10);
  Serial.println("ns");
  delay(5);
}

Resultaterne er således:

true false
if 3 4
if-else 5 4
if-else if 5 7

Den venstre kolonne angiver hvilken sprog struktur, der er brugt. OBS. "if-else if, true"-værdien angiver situationen, hvor cond1 er true. Disse tre strukturer er brugt:

//if
if(cond){

}

//if-else
if(cond){

}else{

}

//if-else if
if(cond1){

}else if(cond2){

}

Det ses at det rene if-statement tager længere tid at eksekvere, hvis cond er falsk, end hvis det er sandt. Dette skyldes højst sandsynligt at der kun forekommer et "jump" i maskinkoden, hvis cond er falsk. Samtidigt ses det at if'en tager længere tid, hvis den er efterfuldt af en else, hvis cond er sand. Det betyder altså at hvis du kobler en if med en else, tager den 2 maskininstruktioner mere, hvis cond er sand. Det ses også at false tager lige så lang tid med else, som uden else. Dette giver også mening, da det er en "jump"-instruktion i begge tilfælde, de referere bare til forskellige steder i koden.

Til sidst ses det at en if-else if struktur tager 5 maskininstruktioner at eksekvere, hvis den er sand. Dette er også at forvente, da den jo bliver efterfulgt af en else. Men den tager 7 hvis den er falsk. Dette kan godt give en umiddelbar forvirring, men ved lidt overvejelse, giver det god mening. Den if, der bliver kørt efter else, er ikke koblet sammen med en else. Dette betyder at den kun tager 3 instruktioner at eksekvere. Det resulterer altså i at Arduinoen først bruger 4 instruktioner til at "jump" til else, og herefter tager en if uden else jo 3 instruktioner. Regne stykket bliver 4 + 3 instruktioner, hvilket er 7.

Man kan nu udregne hvor mange instruktioner en vilkårlig if-else struktur vil tage.

void funktion

Void funktionens antal instruktioner er målt ved at finde tiden det tager, at kalde funktionen 10000 gange. Den er defineret som følgende:

void test(){
  PORTD = B10000000;
}

Her er PORTD = B10000000 brugt ind i funktionen, fordi funktionen skal have noget indhold, ellers bliver den optimeret fra af compileren. Vi kender også, at den specifikke operation tager 1 maskineinstruktion.

Tiden det tager at kalde funktionen 1 gang er målt til 62,843 ns, det vil sige, 1 maskineinstruktion.

return funktion

Denne afsnit er ikke lavet endnu.

funktioner med parametrer

Denne afsnit er ikke lavet endnu.

Referencer