Je maakt het jezelf hier te gemakkelijk, ten eerste ga je er vanuit (of is dat ook zo?), dat bytecode zo is opgezet dat het alle benodigde informatie voor het efficient omzetten naar native Itanium code bevat (zeg maar de interne state van een compiler).
Dat heb je goed gezien. Een systeem als Java en .NET is eigenlijk een conventionele compiler, die in twee stukken is geknipt op het punt waar de interne syntaxtree geoptimaliseerd is, en vertaald gaat worden naar native code.
Je hebt dus een compleet overzicht van de gebruikte variabelen, functies, branches, loops etc.
En je kunt het ook redelijk makkelijk uitbreiden met extra hints in de bytecode, die door oudere systemen genegeerd kan worden, zodat je nieuwere systemen effcienter kunt maken zonder compatibiliteit op te geven.
In feite is het hetzelfde als wat men nu al doet bij x86-code in hardware. Alleen is software flexibeler. En het voordeel is dat de complexiteit niet meer in hardware zit, zodat je een veel groter transistorbudget overhoudt om de hardware sneller en efficienter te maken.
Maar wel op het nivo van de instructies, de bundeling ervan en de afhankelijkheden tussen bundels. Allen niet op het nivo van de bytecode dus, waardoor de optimalisaties op runtime zullen moeten gebeuren. Tenzij je in iedere instructie bundel maar 1 of 2 effectieve instructies plaatst en de itanium core degradeerd to een implentatie ala pentium 60 met meer MHz en cache.
Maar dit is redelijk triviaal. Natuurlijk is Intel niet dom, en heeft men de bundles zodanig ontworpen dat ze simpel en efficient samengesteld kunnen worden. Ik beweer dat deze bundles makkelijker te vullen zijn dan dat je x86-code kunt genereren met een vergelijkbare efficientie.
Het goed bundelen van de instructies en bepalen of 'likely taken' en 'likely not taken' branches beide uitgevoerd moeten worden en het toepassen van predication, software register renaming en dat soort zaken zijn verre van eenvoudig.
Bundelen en register renaming zijn triviaal.
Branch predication hoeft niet at runtime gedaan te worden. Dat is een statisch gebeuren, onafhankelijk van de native code. Ook JIT-code kun je natuurlijk tijdens het ontwikkelen al profilen. Daar verandert at runtime niets meer aan.
Als je daarbij in ogenschouw neemt dat een JIT niet meteen in de eerste run zo ver optimaliseerd als een reguliere compiler zou doen, dan zie je dat het probleem alleen maar groter wordt i.p.v. kleiner dus....niet vrij triviaal dus om het goed te doen.
Dit is redeneren vanuit de x86-wereld. Bij x86 is het inderdaad bijzonder lastig om efficiente code te krijgen, en daarom wil je dat beperken tot de bottlenecks.
Bij een moderne orthogonale architectuur valt de code bijna als vanzelf op z'n plaats, omdat de instructieset als zodanig ontworpen is. In dat geval is optimaliseren op instructieniveau WEL triviaal. En dat is dus precies het punt dat ik probeer te maken.
Voor x86 is het een stuk eenvoudiger wat betreft instructie selectie, register gebruik en optimalisaties (minder noodzakelijk door aggressive OOOE). Het enige waar ik je gelijk in geef is dat de x86 instructies complexer gecodeerd zijn. Niet iets waar volgens mij de echte bottleneck zit bij JIT code generatie (tenzij je verre van optimale code genereerd).
Dat is de omgekeerde wereld. Juist omdat x86 zo complex is, is de behoefte aan OOO zo groot. OOO is nu op een punt dat de compiler niet zo heel hard meer hoeft te werken (mits deze zich aan een beperkte subset van instructies houdt). Ook mede doordat geheugen en cache dermate ver achterop zijn geraakt dat de CPU sowieso minder efficient hoeft te zijn, en kan zijn, en er dus meer ruimte is voor OOO en dergelijke.
Toen de Itanium ontwikkeld werd, was men nog niet op dat punt aanbeland.
Maar je zit bij x86 nog steeds met bepaalde rare problemen met codegeneratie die je bij andere architecturen niet hebt. Zoals het tekort aan registers, waardoor je veel registers moet spillen op de stack... en de vreemde instructieencoding mede door variabele instructielengte, waardoor je dus per instructie redelijk veel tijd kwijt bent om deze op te bouwen.
Dan zijn er ook nog rare dingen zoals shifts of rotates met een variabele, die alleen in het cl-register kan. Dus moet je daarvoor altijd code schrijven die op dat moment het cl-register met de juiste variabele geladen heeft... etc etc.
Conclusie: x86 is lastiger.
En als je een betere processor had, had je heel die OOO-logica niet nodig, of die enorme instructiedecoders... Denk je eens in hoeveel effcienter een x86 al kan worden als je de code gewoon als micro-ops aan de pipeline kunt aanleveren, ipv verpakt in rare x86-instructies met virtuele registers die weer gerenamed moeten worden naar de registers in de hardware etc.
En ja, er zijn rare regeltjes, maar voor zover ik ze nog ken uit mijn 286 assembler tijdperk bestaan deze voornamelijk op het nivo van de instructies zelf en zijn daardoor minder van belang.
Ik snap deze conclusie niet. De JIT-compiler doet JUIST alleen dingen op het niveau van de instructies zelf. Het compilen en optimaliseren op het hogere niveau is immers al gedaan voordat de bytecode gegenereerd werd.
Abstractie is vaak handig...maar abstractie om de abstractie is dat niet. Wat maakt de bytecode van (.NET / java, etc) zoveel beter dan welke native ISA dan ook, en is dat dan voor alle type software zo?
Zoals ik al zei, door de complexiteit van hardware naar software te verplaatsen, kun je veel efficientere hardware ontwerpen.
We zijn toch al lang op het punt aangekomen dat we de hardware eigenlijk optimaliseren aan de hand van onze compilers, en niet zozeer andersom.
Bijkomende voordelen zijn oa dat je platform-onafhankelijke code kunt maken zonder dat daarvoor de sourcecode verspreid hoeft te worden, en op iedere machine door de gebruiker gecompileerd moet worden... En dat je bepaalde optimalisaties kunt uitvoeren die met een statische compiler onmogelijk zijn. Denk bv aan het inlinen van DLL-functies, of het uitrollen van loops met constanten die pas tijdens runtime bekend zijn.
En als er morgen een radicaale betere architectuur gevonden wordt, is de vertaalslag van bytecode naar die nu nog onbekende architectuur dan niet juist de killer?
Dat is moeilijk te zeggen zonder te weten wat die radicale beter architectuur is. Het is echter wel aannemelijk dat we onze huidige programmeertalen aan willen houden, dus dat we in ieder geval op een dergelijke nieuwe architectuur ook C++, C#, Delphi, VB en vergelijkbare talen willen gebruiken.
Aangezien de bytecode in feite een afspiegeling is van deze talen en hun mogelijkheden (een abstractie lager, halverwege programmeertalen en native machines), is het waarschijnlijk dat deze bytecode bruikbaar blijft zolang we op deze manier willen blijven programmeren.
Bytecode is immers een representatie van deze sourcecode in een voor de machine handig te bewerken formaat met de variabelen en constanten alvast in tabellen opgeslagen, en operaties gecodeerd in een enkele byte, zodat simpele lookuptables en switch-statements genoeg zijn om vertalingen uit te voeren. De overhead hiervan is verwaarloosbaar.
Kijk, alles wat in software gedaan kan worden kan in princiepe ook in hardware en dan sneller.
Dat klopt, en dat is ook het punt... Zodra je los komt van x86, en een abstracte bytecode gaat gebruiken, kun je hardware gaan ontwikkelen die deze bytecode zo efficient mogelijk uitvoert. Je krijgt dan hardware die veel beter aansluit op de populaire programmeertalen dan onze huidige x86-hardware, die ontworpen is in de tijd dat programmeertalen nog te veel overhead hadden, en men met de hand code schreef. Dat is een heel ander type code, en vereist een heel ander type processor. Vandaar dus dat ik de x86 zo'n enorme handrem op de ontwikkelingen vind. We zitten allang in een ander tijdperk qua software-ontwikkeling, maar we hebben de hardware nog steeds niet.
Wat bepaald of een architectuur interesant is voor JIT? Is dat iets persoonlijks?
Denk eens logisch na... Wat moet een JIT-compiler doen?
Die moet bytecode omzetten naar machinecode, en liefst code die zo efficient mogelijk is.
Wat wil je dan dus van je architectuur? Daar wil je dat ten eerste de instructieset logisch in elkaar zit, zodat je makkelijk de code kunt uitschrijven.
Ten tweede wil je dat de regels voor efficiente code zo simpel mogelijk zijn.
x86 heeft heel veel uitzonderingen, waar een compiler weinig mee kan, zeker als deze in runtime loopt, zoals een JIT-compiler.
Moderne architecturen zitten veel 'systematischer' in elkaar, waardoor de code dus veel makkelijker algoritmisch te schrijven is, en code eigenlijk bijna als vanzelf efficient is.
Neem een P4 als voorbeeld... die heeft een shift-unit die early-out heeft... je hebt 2 tot 4 clks nodig om een bitshift uit te voeren...
In sommige gevallen zijn een paar adds dus sneller dan een shift-left... in andere gevallen weer niet. De compiler moet dus analyseren met welk geval hij te maken heeft, en dan de beste optie kiezen.
Als je gewoon een processor hebt die altijd snel shift, dan hoeft de compiler zich daar niet druk om te maken, en bespaar je dus op iedere instructie een hoop tijd, en je loopt niet het risico dat je de verkeerde keuze maakt, en inefficiente code schrijft. Je KUNT namelijk geen inefficiente keuze maken.
Overigens is dit natuurlijk niet specifiek voor JIT, ook statische compilers hebben hier voordelen van. Dat was al de insteek sinds de eerste RISC-processors.
Sorry hoor, als ik heden ten dagen na al die jaren sun's java ondanks de snellere hardware nog steeds als traag ervaar, en regelmatig software ziet gebaseerd op .NET met dezelfde problemen dan moet je niet op de proppen komen met het argument dat ik niet zie wat jij in je lab zie.
Voor een deel is dat te wijten aan wat ik het 'VB-syndroom' noem: veel van die code wordt geschreven door beginnende/onervaren programmeurs, omdat de taal een lage leercurve heeft.
Dit kun je ook maken met Java:
http://www.pouet.net/prod.php?which=10808
Als je ziet hoe soepel dat loopt, hoe compact de code eigenlijk is, en dan bedenkt dat dit GEEN hardware-acceleratie is, maar allemaal puur software-rendering, inclusief texture filtering en post-processing etc... ja... vind je Java dan echt heel traag?