Tuesday, May 20, 2014

Test-/skrivelab

Dato:
20 Maj 2014
Varighed:
7 timer

Status

1. Opsæt test-miljø så det kan bruges i rapporten.
2. Skriv om eksperimentet.
3. Avoid-adfærden virker slet ikke.
4. Kontakt 'Henrik' ift. kode.
?. Gennemgå rapport for navngivning.
?. Snak med vejleder om rapporten, på mandag
?. Overbliksbillede over leJOS til EV3.
?. Test af opladning.
?. Skriv om Rugwarrior og om leJOS introduktionsvejledning til adfærd

Mål

Gennemgang af rapport med Ole
Opstil eksperiment (IR-sensor + trådtest?)
Skriv om eksperiment (IR-sensor + Trådtest)

Eksperimentet (IR-sensor)

Dette er en opsummering af den samlede erfaring der er tilegnet over projektet, ift. IR-sensorene samt den problemstilling der er omkring dem. Grunden til at denne opsummering er nødvendig, er at arbejdet med at forstå problemet har været langvarrigt, besværligt og fyldt med manglende forståelse.

leJOS med IR-problem

Rugwarrior med IR-problem

Som det kan ses herover, så er der signifikant forskel på hvordan de to implementeringer får de samme beregninger til at undmynte sig i konkret opførsel. Dette var en større overraskelse end hvad godt var, og stemte slet ikke overens med forventningerne om en overordnet set ens udførsel.

leJOS med fejlen
Herover ses hvordan processor-kraften fordeles imellem de forskellige adfærd for video 1 med leJOS. Hver gang adfærd påbegyndes skrives der til loggen. Denne adfærd kører så videre så længe "NO_CHANGE" varer for arbiteren.
Her kan altså ses, at det tog lidt tid inden SeekBall kom ordenligt igang (opstart af EV3 er lidt langsom ift. sensore), men at den derefter kører uafbrudt indtil Attack overtager. Billedet er baseret på gemt data fra kørslen i videoen ovenfor (leJOS med fejl).


Rugwarrior med fejlen
 Her skrives til loggen, hver gang en given adfærd ønsker at påvirke motorene. Dribble og DriveRandom ønsker altid at skrive til motorene, hvilket giver en fin "baseline" for hvordan sådan et tilfælde ser ud.
Her er det desværre tydeligt at der er utrolig langt imellem hvornår SeekBall (den adfærd der har prioritet når robotten ikke har bolden, men kan se den) ønsker at opdatere motor-værdierne. Problemet er ikke at SeekBall ikke har prioritet jf. adfærdshierakiet, men derimod, at SeekBalls tråd 37 skrivninger til loggen på de 27½ sekund der er skrevet til loggen. Det betyder det at seekballs udregninger tager 743 ms om at blive gennemført!
Det interessante er at disse udregninger er identisk med de udregninger leJOS-implementeringen benytter! Forskellen er "blot" at disse udregninger nu er adskilt ud i deres egen tråd... Hvad forskel kan det mon gøre?

Den normale udgave af leJOS (frameworket) til EV3 har kun en klasse til benyttelse af IR-sensoren der er meget begrænset. Den kan kun levere intevaller af 30grader, ift hvor bolden er, relativt til "ligeud". Dette er en betydelig forringelse ift. leJOS (frameworket) til NXT. Af denne grund valgte jeg at kopiere den tidligere implementering over i mit projekt, og benytte den i stedet. Den relevante kode-del er her:

public int getSensorValue(final int id) {
   int register = 0x4A; // mode = AC
   if (id < 1 || id > 5)
      throw new IllegalArgumentException("The argument 'id' must be between 1 and 5");
   getData(register + id - 1, buf, 1);
   return 0xFF & buf[0];
}

Denne getSensorValue kaldes så 5 gange for hver af den indre sensor-retninger i IR-sensoren. Efter undersøgelse af dette besynderlige problem, kunne jeg afgrænse problemet til denne del af koden... Men hvorfor virker det så for leJOS-implementeringen?
Et bud er at tråden, der mappes til en posix-tråd, bliver markeret som en IO-tråd af operativsystemet...
Dette er dog noget ganske nyt! Det betyder også at problemet burde kunne løses ved at mindske mængden af gange der laves IO-operationer:

public void fetchSample(final float[] sample, final int offset) {
   int register = 0x4A;
   getData(register, valueBuffer, 5); // GOOD!
   for (int i = 0; i < 5; i++)
      sample[i] = 0xFF & valueBuffer[i];
   // for (int i = 0; i < 5; i++)
   //    sample[i + offset] = getSensorValue(i + 1);// BAD!
}

Denne implementering løste problemet (laver 1 læsning af længde 5, i stedet for 5 læsninger af længde 1). Det fik i hvert fald robotten til at køre ordenligt og gav et andet billede i log-filen. 53 opdateringer på 19 sekunder. Dvs. 358 ms pr opdatering.
Rugwarrior uden fejlen
Desværre betyder alt dette, at tråde er et emne der skal undersøges ret grundigt. Pludsligt kom implementeringsdetaljer i java, til at blive påvirket af hvilke beslutninger operativsystemet tog. Hvis Rugwarrior ordenligt skal kunne konkurrere med leJOS-implementeringen, så skal dette kunne skjules/forsimples for programmøren, da det er utrolig svært at gennemskue hvordan en tråd vil blive behandlet af operativsystemets scheduller - tilføj til kompleksiteten, at leJOS til EV3 kun benytter en skrabet udgave af et fuldt operativsystem, så mange af debug-redskaberne er ikke installeret!

En given adfærd får altså udover den prioritet jeg giver den, en processor-prioritet alt efter hvor mange og hvor besværlig sensor-information den skal bruge til sine udregninger.

Hele denne utilsigtede undersøgelse giver blot flere bekymringer og overvejelser der naturligt skal undersøges grundigt. Dette kan opsummeres til:
Hvad er tråde ift. leJOS til EV3 og hvilke egenskaber har de?

Trådtest (opsummering af tidligere eksperiment)

Denne test gik ud på at se om problemet kunne løses ved at ændre prioritet af tråde, således at en given tråd ikke blev lavere prioriteret pga. IO-forspøgelser (sensor-målinger). Testen er forholdvis simpel. Start et program, der starter 2 tråde. Hver tråd laver cpu-intensive beregninger, der har en side effect (dvs. resultaterne bruges til sidst i programmet, så de kan ikke ignoreres). Dernæst, se hvad der sker når en tråd har en anden prioritet end en anden.
Ens prioritet:
Det forventede resultat var at begge tråde ville "tælle op" lige hurtigt.
Dette resultat matchede eksperimentet.

Forskellig prioritet:
Det forventede resultat var en af 2 forskellige:
  1. Den lavest prioritede tråd talte aldrig op (fastholdelse af prioriteterne)
  2. Den laveste proriterede tråd talte langsommere op (aging -> tråden fik midligertidigt højere prioritet, når den blev starved længe nok)
Der der skete var dog følgende: Ingen målbar forskel på forskellig og ens prioritet.
En anden interessant problemstilling var dog at programmet heller ikke stoppede, som det skulle. Den boolske værdi som "main"-tåden satte for hver af de to "beregningstråde", der skulle sikre at de stoppede vha. cooperative threading, havde et problem. Problemet er at finde i Java's Memory Model (JMM) og er til dels en smule pinlig - men så tilstrækkeligt vigtig og kritisk at den skal trævles igennem.

public void stopWork() {
   doWork = false;
}

Denne metode blev brugt til at sætte den boolske værdi i trådene til falsk. Og værdien BLIVER faktisk sat til falsk. I main memory. Tråde har dog lov til at benytte caching til local memory og det er uforudsigeligt hvornår tråden vælger at opdatere fra main memory - dvs. den behøver aldrig at opdatere fra main memory, mm. dens interne logik gør det nødvendigt. Dette kommer fra JMM.

Grunden til at jeg benyttede denne fremgangsmåde, uden nærmere eftertanke eller overvejelse ift. concurrency-problemer, er at denne måde at stoppe tråde på bliver brugt et andet sted i mit lego-projekt: leJOS-implementeringen.

public void suppress() {
   suppressed = true;
}

Denne metode har præcis samme problem. Alle steder i programmet hvor dette har været brugt til at kontrollere flowet af kontrol i programmet, har denne kode været brugt. Og den har været forkert. Ikke nok med at den er forkert, så er den baseret på leJOS tutorial der siger at man skal benytte en boolsk værdi, til at angive hvornår tråden skal undertrykkes... Dette er dog ikke nok. Denne boolske værdi skal være volatile, før den virker efter hensigten.
Efter at have spurgt lidt rundt, fandt jeg frem til at ingen jeg har mødt har haft dette i tankerne. End ikke leJOS eksempel (bumper-car) har en volatile suppress (i deres implementation står der endda: "_suppressed = true;// standard practice for suppress methods").

Kort sagt, et komplekst problem, der er svært at opdage og som kan forårsage uventede og utilsigtede problemer.

Skrivning/feedback

Feedback modtaget og rettearbejdet påbegyndt. Feedback betyder også, at det ikke giver mening at påbegynde arbejdet med at få omsat denne blog til rapport, endnu.

No comments:

Post a Comment