2009-06-03 3 views
18

Problem: Ich habe eine Methode, die zu über 8000 Byte Java Bytecode kompiliert. HotSpot hat eine magische Grenze, die dazu führt, dass der JIT für Methoden, die 8000 Bytes überschreiten, nicht aktiviert wird. (Ja, es ist vernünftig, eine riesige Methode zu haben. Dies ist eine Tokenizer-Schleife.) Die Methode ist in einer Bibliothek und ich möchte nicht, dass Benutzer der Bibliothek HotSpot konfigurieren müssen, um das magische Limit zu deaktivieren.Gibt es einen Java-Bytecode-Optimierer, der nutzlose gotos entfernt?

Bemerkung: Das Dekompilieren des Bytecodes zeigt, dass der Eclipse Java Compiler viele sinnlose GOTOS generiert. (Javac ist noch schlimmer.) Das heißt, es gibt Gotos, die nur von Sprüngen aus erreichbar sind. Offensichtlich sollte der Sprung, der zum Goto springt, stattdessen direkt dorthin springen, wo der Goto springt und der Goto sollte eliminiert werden.

Frage: Gibt es einen Bytecode-Optimierer für Java 5-Klassendateien, der sinnlose Sprungketten abflacht und dann unnötige gotos entfernt?

Edit: Ich meine Muster wie:

8698: goto 8548 
8701: goto 0 

Offensichtlich kann die zweite gehe nur durch einen Sprung auf 8701, die einen direkten Sprung auf 0

als auch sein könnte, auf einem zweiten erreichbar Untersuchung dieses fragwürdige Muster ist häufiger:

4257: if_icmpne 4263 
4260: goto 8704 
4263: aload_0 

Wo offensichtlich, möchte man den Compiler den „ungleich“ Vergleich auf „gleich“ -Vergleich umkehren, springt zu 870 4 und beseitigen Sie den goto.

+3

Einige Architekturen haben eine Grenze, wie weit eine relative Verzweigung gehen kann (weil sie die Adresse in einem 8- oder 16-Bit-Register haben), so dass sie oft mit einer relativen Verzweigung zu einer nicht relativen Verzweigung, die die vollständige Verzweigung verwendete Programmzählergröße. Ist die JVM so? –

+0

Meinst du * LABELS * nur von Sprüngen erreichbar? –

+0

Eine Laufzeit Annotation, um einen Hinweis auf die JVM zu geben, wäre in diesem Fall sicher nett zu sein .... aber ich glaube nicht, dass so etwas existiert (und ein schnelles Google macht nichts.) – Jared

Antwort

0

Eine Methode kompiliert zu über 8000 Bytes? Versteht irgendjemand diesen Code? Ist es testbar? Versuchen Sie, es in mehrere (private?) Methoden mit aussagekräftigen Namen aufzuteilen, anstatt sich mit dem Optimierer zu beschäftigen!

OK, vielleicht gibt es Fälle legitime große Methoden. Aber leider gibt es keine Hinweise in der Frage.

+1

Hört sich an, als würde er einen Parser schreiben - es ist tatsächlich sehr vernünftig, wenn eine Tokenizer-Schleife groß wird, und sie ist * * * * besser lesbar, um sie zusammenzuhalten. (Ich hatte Leute zwingen mich, eine solche Methode zu brechen, nur weil es über n Zeilen war und im nächsten Monat beschwerten sie sich, weil es schwerer zu folgen war ...) –

+2

Ja, ich verstehe den Code. Es wird auch genauer als alle Umformulierungen als rekursive Abstiegsmethoden abgebildet. Ja, es ist mit einer großen Anzahl von Komponententests testbar. – hsivonen

1

Ich fühle deinen Schmerz. Ich musste einmal einen Parser schreiben, der ungefähr 5kloc von if (str.equals (...)) Code hatte. Ich habe mehrere Methoden nach dem Prinzip von parse1, parse2 usw. durchgebrochen. Wenn parse1 nicht zu einer geparsten Antwort geführt hat, wurde parse2 aufgerufen usw. Dies ist nicht unbedingt die beste Vorgehensweise, aber es tut, was Sie brauchen .

+1

BTW: für den Fall, dass Sie nicht zufällig wissen, haben Sie das "Chain of Responsibility" -Muster mit dem Ansatz parse1, parse2 usw. implementiert (nicht, dass das eine schlechte oder gute Sache für dieses Problem ist, wollte nur erwähnen. ..) –

+0

Ich hatte mehrere Methoden anstelle von einem großen Schalter in einer Schleife. Die große Switch-Struktur passt jedoch besser zur Spezifikation und ist schneller, wenn der JIT einsetzt. – hsivonen

+0

Nun, Sie könnten immer einen kleineren Schalter() verwenden und der Standardfall ruft eine andere Methode mit einem kleinen Schalter auf und so weiter, bis Sie alle größer sind Schaltergehäuse sind abgedeckt. Dies wäre nicht so schön wie eine große Switch-Tabelle, aber es würde funktionieren. In meinem Fall konnte ich keinen Schalter verwenden, da er nicht auf Strings funktioniert. – KitsuneYMG

0

Macht es einen Unterschied, wenn Sie nicht mit Debug-Symbolen kompilieren (d. H. Das Flag -g in javac)? Das könnte die Methode unter die magische Grenze bringen.

+0

Zumindest im Eclipse Java Compiler hat dies keinen Einfluss auf die Anzahl der Bytecodes in der kompilierten Methode. (Ich nehme an, die Debug-Tabelle ist separat.) – hsivonen

0

Wäre es nicht möglich, die Methode in Submethoden umzuformen? Moderne JITs inline diese Anrufe sowieso.

+2

Wenn Sie zuvor noch keinen Parser handgeschrieben haben, kann manchmal der Scanner (Lexer/Tokenizer) groß werden, aber die Lesbarkeit kann bei der Aufteilung stark nachlassen. Natürlich habe ich seinen Code nicht gesehen, aber ich war schon dabei ... –

+0

Ich habe bereits die Tokenizer-Aktionen aufgeteilt, die aufgeteilt werden können, ohne den Kontrollfluss zu beeinflussen. Die Kontrollstruktur selbst ist riesig. – hsivonen

+1

@scott, ich habe einen Parser vorher handgeschrieben. Wenn es (aus irgendeinem besonderen Grund) zu komplex ist, könnte es ein vernünftiger Zeitpunkt sein, einen geeigneten Parsergenerator in Betracht zu ziehen. –

0

Wenn es eine Tokenizer-Schleife ist, wäre es besser, es mit einem Data Drivven Satz von Mappings und ein wenig Nachdenken zu tun, wie es angemessen ist?

Sie würden also Ihre Token-Matches in einer Struktur speichern, die sie mit Daten zur Syntax dieses Tokens und Methoden zur Implementierung der zugehörigen Funktionen abbildet. Nachschlagen kann über die Struktur optimiert werden und Sie vermeiden die große Schleife.

Das führt zu dem Problem, die Daten und die Implementierung synchron zu halten, aber Sie könnten die Daten von Ihrer Codebasis mit einem Doclet oder möglicherweise Annotation generieren.

Ohne genau zu wissen, was Ihre große Methode macht, beschränken wir uns darauf, sie so zu optimieren, wie Sie es für am besten halten (und das ist anscheinend sowieso nicht möglich).

+0

Nein, idealerweise möchten Sie, dass die Zustandsübergänge auf Sprünge in Code-Daten-Lookups kompiliert werden. – hsivonen

+0

Sicher hängt das eher davon ab, ob Sie die Suche optimieren können - sowohl durch effizientes Suchen des Match-Space als auch durch Caching der Ergebnisse. Eine große, fest codierte Reihe von Token-Matches ist möglicherweise ziemlich resistent gegen Optimierungen (was vermutlich darauf zurückzuführen ist, dass Sie nach JIT-Hilfe suchen). In einem früheren Projekt verwendete ich zwischengespeicherte Methodenreferenzen mit ziemlich guten Ergebnissen. Änderungen in der 'Quelle' mussten nur den Verweis auf einen neuen Lookup ungültig machen, und die normale Ausführung konnte mit hoher Geschwindigkeit ausgeführt werden, wobei die Methodenaufrufe ausgelöst wurden, ohne dass es zu Leistungseinbußen aufgrund des Tokenabgleichs kam. – AndyT

0

erhöht sich Ihre Leistung, wenn Sie einen Bytecode-Shroker/Obfuscator auf Ihrer Klasse ausführen? z. B. yguard, proguard, ...

vielleicht können Sie einen Klasse-Datei-Postprozessor mit Asm schreiben, weil Ihr Anwendungsfall so spezifisch ist.

auch wenn du alle sinnlosen gotos entfernst, bringt dich das unter die magische grenze?

0

Eine Liste von bytecode libraries erwähnt BCEL und ASM, dass ich zuvor gehört hatte, zusammen mit vielen anderen verschiedenen Dinge zu tun.