INF2440 Uke 8, v2015 : Om Oblig 3, Ulike Threadpools, JIT-kompilering Arne Maus PSE, Inst. for informatikk 1
Hva har vi sett på i uke 7 1. Mer om matrisemultiplikasjon 1. Radvis (ikke kolonnevis) beregning av C 2. Transponering kan også gjøres parallell (last-balansering) 2. Modell2-kode for sammenligning av kjøretider på (de fleste) parallelle og sekvensielle algoritmer. 3. Hvordan lage sin egen threadpool for ett problem 4. Hvordan lage en parallell løsning ulike måter å synkronisere skriving på felles variable 5. Ulike strategier for å dele opp et problem for parallellisering: 2
Hva skal vi se på i uke 8: 1. Om oblig3 å finne den konvekse innhyllinga til n punkter sekvensielt Generelt om parallellisering rekursivt i neste uke og drøfting av hvordan bruke det på Oblig 3 2. En effektiv Threadpool? Executors.newFixedThreadPool 3. Mer om effektivitet og JIT-kompilering! 4. Om problem(?) mellom long og int 3
Den konvekse innhyllinga til n punkter Oblig 3 Hva er det, definisjon Hvordan ser den ut Hva brukes den til? Hvordan finner vi den? 4
1) Oblig 3, problemstilling Vi skal finne den konvekse innhyllinga til n punkter p i xy-planet. Her er 100 tilfeldige punkter og deres innhylling: en rekke med linjer fra punkt til punkt i mengden slik at alle andre punkter er på innsida av denne mangekanten. Mangekanten er konveks, dvs alle indre vinkler er 180 Altså ikke linje 91-94 og 94-95.. Alle punktene på en slik innhylling er med: Altså: linje 91-95 og 95-99, ikke linje direkte: 91-99. 5
Hva bruker vi den konvekse innhyllinga til? Innhyllinga er en helt nødvendig første steg i flere stegs algoritmer innen : Spillgrafikk (modellerering av flater, mennesker, ansikter, hus, borger, terreng,.. osv) Kartografi Høydekart over landskap Sjøkart volumberegninger innen olje-prospektering. Er f.eks. starten på en Delaunay-triangulering av punktene. De etterfølgende figurer er laget i Geogebra. Anbefales sterkt (gratis) last ned: http://geogebra.no/ 6
Først en enkel geometrisk sats, I Ligningen for en linje Enhver linje fra et punkt p1 (x1,y1) til p2(x2,y2) kan skrives på formen (trivielt forskjellig fra slik Geogebra gjør det): ax + by + c = 0 Hvor: a = y1 y2, b = x2 x1 og c = y2 x1 y1 x2. Merk at dette er en rettet linje fra p1 til p2. 7
Først en enkel geometrisk sats, II Figur2. En linje fra p1 (1,2) til p2 (7,4) har da linjeligningen: a = y1 y2, b = x2 x1, c = y2 x1 y1 x2. 2 4 x + 7 1 y + 4 1 2 7 = 0; dvs: 2x + 6y 10 = 0 8
Avstanden fra et punkt til en linje, I Setter vi ethvert punkt på p(px,py) linja, vil linjeligninga gi 0 som svar (per definisjon): ax + by + c = 0 Setter vi inn et punkt som ikke er på linja (p4 eller p3) vil vi få et tall som er : negativt (<0) hvis punktet er til høyre for linja, sett i linjas retning: p1 til p2 positivt (>0) hvis punktet er til venstre for linja, sett i linjas retning: p1 til p2 linjas retning 9
Avstanden fra et punkt til en linje II Avstanden fra et punkt til en linje (vinkelrett ned på linja) er : d = ax + by + c a 2 + b 2 Jo lenger fra linja punktene et desto større negative og positive tall blir det. Setter inn p3 (5,1) i linja p1-p2: -2x+6y-10 = 0, får vi d= : 2 5+6 1 10 40 = 14 6,32 = 2,21.. linjas retning 10
En linje deler da planet i to: Punktene til høyre og til venstre for linja (sett fra rettet linje fra p1 til p2) + linjas retning _ Vi er nå interessert i de punktene som ligger lengst fra én gitt linje ax + by + c = 0 Kan da avstandsformelen forenkles gjøres raskere? d = ax + by + c a 2 + b 2 11
To observasjoner: Punktene med minst og størst x-verdi ligger på innhyllinga (A og I) De punktetene som ligger lengst fra (positivt og negativt) enhver linje p1-p2, er to punkter på den konvekse innhyllinga.(p og K) Vi skal etter starten av algoritmen bare se på det punktet som ligger i mest negativ avstand fra linja. 12
Algoritmen for å finne den konvekse innhyllinga sekvensielt 1. Trekk linja mellom de to punktene vi vet er på innhyllinga fra maxx -minx (I - A ). 2. Finn punktet med størst negativ (kan være 0) avstand fra linja (i fig 4 er det P). Flere punkter samme avstand, velg vi bare ett av dem. 3. Trekk linjene fra p1 og p2 til dette nye punktet p3 på innhyllinga (neste lysark: I-P og P-A). 4. Fortsett rekursivt fra de to nye linjene og for hver av disse finn nytt punkt på innhyllinga i størst negativ avstand ( 0). 5. Gjenta pkt. 3 og 4 til det ikke er flere punkter på utsida av disse linjene. 6. Gjenta steg 2-5 for linja minx-maxx (A-I) og finn alle punkter på innhyllinga under denne. 13
Rekursiv løsning: Finn først P (mest neg. avstand fra I-A) Trekk så I-P og finn C, Trekk så I-C, og finn R. trekk så I-R og finn Q. Finner så intet over R-C eller C-P. Trekker P-A og finner så B over. Ferdig. 14
Problemer dere vil møte i den rekursive, sekvensielle løsningen I Hvordan representere et punkt p i? Med indeksen i (ikke med koordinatene x og y)? Debugging (alle gjør feil først) av et grafisk problem vil vi ha tegnet ut punktene og vårt beste forsøk på konvekse innhylling. Klassen TegnUt (hvis n < 250) Brukes slik fra main-tråden: «Litt» feil: TegnUt tu = new TegnUt (this, kohyll); for (int i =1; i > 0; i--)i+=2; // forsinkelse a) TegnUt tegner ut punktene og innhyllinga i en ArrayList <Integer> kohyll. Skrives trivielt om av deg hvis du lager din egen klasse IntList. this er en peker til main-objektet. b) Forsinkelseslinja gjør at klassen med maintråden ikke terminerer og fjernes før TegnUttråden får lest data fra main-tråden. c) Ikke nødvendigvis proff kode i klassen TegnUt 15
Problemer dere vil møte i den rekursive, sekvensielle løsningen II Finne punktene på den konvekse innhyllinga i riktig rekkefølge? Tips: Du bruker to metoder for det sekvensielle tilfellet: sekvmetode() som finner minx, maxx og starter rekursjonen. Starter rekursjonen med to kall på den rekursive metoden, først på maxx-minx, så minx-maxx: sekvrek (int p1, int p2, int p3, ArrayList <Integer> m) s m, inneholder alle punktene som ligger på eller under linja p1-p2, og p3 er allerede funnet som det punktet med størst negativ avstand fra p1-p2. Du kan la sekvrek legge inn ett punkt: p3 i innhyllinga-lista, men hvor i koden er det? Når legges minx inn i innhyllingslista? 16
Problemer dere vil møte i den rekursive, sekvensielle løsningen III Få med alle punktene på innhyllinga hvor flere/mange ligger på samme linje (i avstand = 0), og få dem i riktig rekkefølge. Tips: Husk at når du finner at største negative avstand er = 0 må du ikke inkluderer p1 eller p2 som mulig nytt punkt (de er allerede funnet) Si at du har funnet p1=31 og p2=5 i fig. Du bør da bare være interessert i å finne de punktene som ligger mellom p1 og p2 på linja (60 og 73) Du må teste om nytt punkt p3 har y og x-koordinater mellom tilsvarende koordinater for p1 og p2. Da finner du ett av punktene med kall på sekrek over linja p1-p2 (31-5). Gjenta rekursivt til det ikke lenger er noen punkter mellom nye p1 og p2. Punktene videre oppetter linja (f.eks. 5-95) finnes av rekursjon tilsvarende som for 60 og 73. 17
Problemer dere vil møte i den rekursive, sekvensielle løsningen IV Hvordan stoppe rekursjoinen? Tips: Se på antall punkter som nå ligger på eller utenfor den linja vi skal teste. 18
2) En effektiv Threadpool? Vi har med modell2-koden selv startet en samling av k tråder som- venter til vi vil kjøre ett problem (evt. flere ganger): enten med samme n for å få bedre tider (median) eller for en ny n. Ideen om å lage en samling av tråder som er klar for å løse neste oppgave har vi også i Java-biblioteket. Vi skal her se på: Executors.newFixedThreadPool i java.util.concurrent. Her kan vi også bytte ut problemet 19
Grunnidéene i Executors.newFixedThreadPool Du starter opp et fast antall tråder Hvis du under kjøring trenger flere tråder enn de startet må du vente til en av dem er ferdig og da er ledig For hver tråd som gjør noe er det tilknyttet et objekt av klassen Future: Den sier deg om tråden din er ferdig Du kan legge deg og vente på ett eller alle Future-objekter som join() med vanlige tråder Future-objektet bringer også med seg svaret fra tråd når den er ferdig, hvis den har noen returverdi En tråd som har terminert kan straks brukes på nytt fordi main-tråden venter ikke på tråden, men på tilhørende Future 20
For å administrere en slik mengde (pool) av tråder Må man først lage en pool (med tilhørende Vektor av Futures): class MinTraadpool{ MinTraadpool pt = new MinTraadpool (); int anttraader = Runtime.getRuntime().availableProcessors(); ExecutorService pool = Executors.newFixedThreadPool(pt.antTraader); List <Future> futures = new Vector <Future>(); Hvordan lage trådene og slippe dem ned i poolen (svømmebasenget): for (int j =0; j < anttraader; j++) { Thread QParExec = new Thread(new FindExec(j)); futures.add(pool.submit(qparexec)); // submit starter trtåden } 21
For å administrere en slik mengde (pool) av tråder Slik venter man på framtider (Futures) get returnerer svaret (hvis noe ikke her) while (!futures.isempty()) { Future top = futures.remove(0); try { if (top!= null) top.get(); } catch (Exception e) { System.out.println("Error Futures");} } Trådene som startes er vanlige (indre) klasser med en parallell metode run() : class FindExec implements Runnable { int index; public FindExec(int ind) { index = ind; } public void run() { <kall parallell metode> }} 22
Mange muligheter med slike pooler: Man kan lukke poolen: pool.shutdown(); Man kan lage ca. 13 ulike typer av pooler: Fast størrelse, variabel størrelse, cachete (gjenbruk), med tidsforsinkelse, med og uten ThreadFactory (et objekt som lager tråder når det trengs),.. Det hele koker ned til spørsmålene: Vi trenger å ha en rekke tråder som parallelliserer problemet vårt Er en slik trådpool effektiv? 23
1 &2)Test på effektivitet tre implementasjoner av parallell FindMax i :int [] a speedup: a) Med ExecutorService og FixedThreadPool a1)med like mange tråder som kjerner: a2) Med 2x tråder som kjerner b) Med modell2 kode med 2 CyclicBarrier for start&vent n a1)pool 1x a2) Pool 2x b) Barrier 1000 0.016 0.037 0,049 10000 0.249 0,235 0,011 100000 0.256 0,302 0,105 1000000 0.469 0,399 0,453 10000000 1.353 1,344 1,339 100000000 1.615 1,6662 1,972 Konklusjon: Om lag like raske!? 24
3) Nok et problem/overraskelse med JIT-kompilering Som vi husker går JIT-kompilering i flere steg: Oversettelse til maskinkode av hyppig brukt kode Mer bruk optimalisering av den maskinoversatte koden Si bruk > 50 000 ganger Enda mer bruk super-optimalisering av koden en gang til Først noen resultater; sammenligning av Barrier-løsningen av FinnMax med samme løsning skrevet litt annerledes: Speedup: n b) Barrier Ny Barrier 1000 0,049 0,14 10000 0,011 0,66 100000 0,105 0,57 1000000 0,453 3,40 10000000 1,339 7,69 100000000 1,972 9,38 25
Konklusjon: Hurra, en mye bedre parallellisering? Speedup: 1,972 < 9,38!!! En klart bedre løsning? At speedup er mye større, er det da sikkert at parallelliseringen er mye bedre/raskere? Svar: JA, men bare hvis den sekvensielle koden som parallelliseringen sammenlignes med er den samme. Det eneste som var forskjellig mellom disse to programmene var at jeg hadde forsøkt å gjøre den sekvensielle koden raskere ved å inline - selve koden på kallstedet. Den parallelle koden var den samme. Vi tester og ser etter hva som har skjedd her! 26
Hva var forskjellen mellom de to Barrier-kodene for n = 100 000 000 Fra b) Barrier programmet (>java FinnBarrier.java): Median parallel time :26.697743 Median sequential time:52.128282,, Speedup: 1.98, n = 100000000 Fra Ny Barrier-programmet (INF2440Para\FinnMax>java FinnM ): Max verdi parallell i a:99989305, paa: 23.691937 millisek. Max verdi sekvensiell i a:99989305, paa: 223.66429 millisek. Median sequential time:223.593249, median parallel time:23.84312, n= 100000000, Speedup: 9.38 Konklusjon: Det er særlig den sekvensielle kjøretiden er langsommere, ikke at den parallelle er raskere. 27
Hva var forskjellen mellom de to kodene UTEN JITkompilering, n = 100 000 000 (java Xint BarrierMax..)? Fra b) Barrier programmet: Median parallel time: 533.367279 Median sequential time:1772.424558, Speedup: 3.35, n = 100000000 Fra Ny Barrier-programmet: Max verdi parallell i a:99989305, paa: 541.823362 millisek. Max verdi sekvensiell i a:99989305, paa: 2334.368515 millisek. Median sequential time:2334.184786, median parallel time:546.844936, n= 100000000, Speedup: 4.19 Konklusjon: Det er særlig den sekvensielle kjøretiden som er bedre optimalisert, ca.30x for b) mens bare ca. 10x for NyBarrier! Hvorfor? 28
Her er forskjellen mellom de to programmene Ny Barrier, langsom (10x) sekvensiell optimalisering. Koden for sekvensiell metode er lagt rett inn i en større metode (utfor() ) som ikke kalles mange ganger b) Barrier, mye raskere sekvensiell 34x optimalisering.laget en metode av sekvensiell kode som kalles fra utformetoden. // sekvensiell finnmax t = System.nanoTime(); totalmax = 0; for (int i=0;i < a.length;i++) if (a[i] > totalmax) totalmax = a[i]; t = (System.nanoTime()-t); seqtime[j] =t/1000000.0; int dosequentialversion(int [] a) { int max= -1; for (int i = 0; i< a.length; i++) if (a[i] > max) max = a[i]; return max; } // end dosequentialversion Optimalisatoren i JIT ser ut til å være langt bedre til å optimalisere (små) metoder enn en løkke inne i en større metode. 29
Observasjon JIT-kompilering/optimalisering Vi bør ta hensyn til hvordan optimalisatoren virker Generelt ser den ut til å greie å øke hastigheten på interpretert kode med 10x Små metoder som vi finner i klassen for en bit array for pimtall implementer i en byte-array (med metoder som isprime(i), setnotprime(), nextprime(m), ) og : int dosequentialversion(int [] a) i FinnMax er meget velegnet for optimalisering Så små metoder kan optimaliseres i vårt tilfelle: 1772/52= 34 x Når vi skriver kode bør vi bruke denne kunnskapen fordi Gir enklere kode lettere å skrive Gir raskere kode Grunnen til dette er vel at ved at når programmereren har lagt kode inn i en metode, sier hun at avhengigheten til resten av programmet er begrenset, grovt sett til parameterne. Lettere da å optimalisere! Oppskrift: Best optimalisering hvis en metode bare behandler parameterne og lokale variable i metoden. 30
4) Problemer med forholdet mellom long og int Java prøver hele tiden å være mest økonomisk når den regner ut uttrykk: Regner ut billigst først og så konverterer ved tilordning. public static void main (String [] args) { int m = 2000000000; long tall = m*m; System.out.println("M:"+m+", tall (m*m):"+ tall); tall = (long)m*m; System.out.println("M:"+m+", tall((long)m*m):"+ tall); tall = (long)(m*m); System.out.println("M:"+m+", tall((long)(m*m):"+ tall); tall = (long)m*(long)m; System.out.println("M:"+m+", tall((long)m*(long)m):"+ tall); } M:2000000000, tall (m*m):-1651507200 M:2000000000, tall((long)m*m):4000000000000000000 M:2000000000, tall((long)(m*m):-1651507200 M:2000000000, tall((long)m*(long)m):4000000000000000000 31
Hva har vi sett på i uke 8: 1. En første gjennomgang av Oblig3 Den sekvensielle løsningen m. tips 2. En effektiv Threadpool? Executors.newFixedThreadPool 3. Mer om effektivitet og JIT-kompilering! 4. Om et problem mellom long og int 32