Arduino Tidsforhold Variabeltyper

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

Denne side omhandler tidsforhold med variabeltyper i C med henblik på Arduino.

Målingsramme

De forskellige variabeltyper er blevet målt internt i koden, og for at få den mest præcise værdi er alle variabeltyper 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 variabeltyper, 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 detklarerer 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ålingramme. 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.

Målinger

Tiden det tager, at lave en for-loop med én variabel kan bruges til at finde hvor lang tid det tager at definere forskellige typer variabler. Ved at definere en ny variabel in i den samme for-loop (sammen med control variablen), kan man subtrahere den målte for-loop tid, for at få tiden for udelukkende den nye variabel. Herefter kan man dividere med den antal gange, loopen har kørt igennem (i denne tilfælde, 10.000) for at finde tiden at definér én enkel variabel. Dog skal variablen altid defineres som volatile, så compileren ikke optimerer på den.

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

    volatile bool a = 0

    control = 0;
  }
  ultime = micros() - ulmicros;

  total += ultime - 5034.05f;

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

Her ses et udsnit af den samme kode som før. Bare hvor en ny boolean defineres in i for-loopen, og vores kontrol for-loop tid trækkes fra tiden det tager, at køre den nye for-loop igennem. Resultatet den printer bliver hvor lang tid det tager, at definere 10.000 booleans til 0. Tallet er et løbende gennemsnit, som bliver mere nøjagtigt med tiden.

SerialBool.png.

Her ses hvordan gennemsnittet konstant registreres i serial output. Efter noget tid, er gennemsnitet målt til 1257,70 mikrosekunder. Det vil sige, at det tager 125,77 nanosekunder at definere én boolean. Ud fra dette, kan man finde hvor mange maskineinstruktioner det tager at definere en boolean. Den Arduino vi anvender er en Arduino Uno, som har en klokfrekvens på 16 MHz.[[1]] Dette tal beskriver hvor mange instruktioner den kan lave pr. sekund. Tiden det tager, at lave én instruktion kan findes med formlen: Mapleklokfrekvens1.png. I vores tilfælde med 16 MHz, får vi en tid på: Mapleklokfrekvens2.png.

Maskineinstruktionsantallet for at definere én boolean kan beregnes ved at dividere 125,77 ns (tiden det tager, at definere én boolean) med 62,5 ns. Der fås 2 maskineinstruktioner for én boolean, med en lille afvigelse på 0,77 ns. Denne test er blevet kørt med 10 forskellige typer variabler, for at bestemme hvor mange maskineinstruktioner der skal bruges for at definere hver. Der er lavet en tabel over resultaterne:

Variabeltype Tid til at definere som 0 Antal maskineinstruktioner
Boolean 125,770 ns 2
Byte 125,770 ns 2
Char 125,770 ns 2
Unsigned char 125,770 ns 2
Word 251,534 ns 4
Int 251,534 ns 4
Unsigned int 251,534 ns 4
Long 503.045 ns 8
Unsigned long 503.045 ns 8
Float 503.045 ns 8

Tiden det tager, at compare med 0 bliver målt til det samme.

Ud fra tabellen kan man se, at afvigelsen stiger lineært med tiden. F.eks skulle 8 maskineinstruktioner tage præcist 500,00 nanosekunder, men vi har registreret 3,045 ns mere end det. Vi beregner, at tiden vi registrerer for alle typer variabler er omkring 0,61% større, end den teoretiske tid. Dette skyldes interrupt, som er en del af micros() funktionen. [2] For at vedligeholde micros(), skal tidsmålingen afbrydes i omkring 6 microsekunder hver millisekund. Det svarer til en tidsmåling på omkring 0,6% større, end den burde være, som passer med det, vi måler.

Referencer