2009-06-03 23 views
18

Problema: ho un metodo che compila su oltre 8000 byte di bytecode Java. HotSpot ha un limite magico che rende il JIT non kick-in per i metodi che superano 8000 byte. (Sì, è ragionevole avere un metodo enorme: si tratta di un ciclo di tokenizer.) Il metodo è in una libreria e non voglio richiedere agli utenti della libreria di configurare HotSpot per disattivare il limite magico.Esiste un ottimizzatore bytecode Java che rimuove i gotos inutili?

Osservazione: la decompilazione del bytecode indica che il compilatore Java di Eclipse genera un gran numero di gotos inutili. (javac è anche peggio.) Cioè, ci sono gotos che sono raggiungibili solo dai salti. Ovviamente, il salto che salta al goto dovrebbe invece saltare direttamente dove il goto salta e il goto dovrebbe essere eliminato.

Domanda: Esiste un ottimizzatore bytecode per i file di classe Java 5 che appiattisce inutili catene di salto e rimuove i gotos non necessari?

Edit: Voglio dire modelli come:

8698: goto 8548 
8701: goto 0 

Ovviamente, la seconda goto può essere raggiunto solo con un salto a 8701, che potrebbe anche essere un salto diretto a 0.

Su una seconda indagini, questo modello discutibile è più comune:

4257: if_icmpne 4263 
4260: goto 8704 
4263: aload_0 

dove ovviamente, si vorrebbe il compilatore per invertire il confronto "diverso" a confronto "uguale", saltare a 870 4 ed elimina il goto.

+3

Alcune architetture hanno un limite su quanto può essere lontano un ramo relativo (perché mantengono l'indirizzo in un registro a 8 o 16 bit) così spesso lo hanno aggirato con un ramo relativo a un ramo non relativo che utilizzava il pieno dimensione del contatore del programma. La JVM è così? –

+0

Intendi * ETICHETTE * raggiungibili solo da salti? –

+0

Un'annotazione di runtime per dare un suggerimento alla JVM sarebbe sicuramente bella in questo caso ... ma non credo che esista una cosa del genere (e un google veloce non mostra nulla.) – Jared

risposta

0

Un metodo di compilazione a oltre 8000 byte? Qualcuno capisce quel codice? È controllabile? Prova a dividerlo in più metodi (privati?) Con nomi significativi invece di complicazioni con l'ottimizzatore!

OK, forse ci sono casi validi metodi legittimi. Ma scusa, non ci sono suggerimenti nella domanda.

+1

Sembra che stia scrivendo un parser - in realtà è molto ragionevole che un ciclo di tokenizer diventi grande, ed è in realtà * più * leggibile per tenerlo insieme. (Ho avuto persone che mi costringevano a rompere questo metodo solo perché era finito le righe e il mese successivo si sono lamentati perché era più difficile da seguire ...) –

+2

Sì, ho capito il codice. Inoltre esegue il mapping più vicino alle specifiche di qualsiasi riformulazione come metodi di discesa ricorsiva. Sì, è testabile con un ampio set di test unitari. – hsivonen

1

Sento il tuo dolore. Ho dovuto scrivere un parser una volta che aveva circa 5kloc di codice if (str.equals (...)). Sono entrato in diversi metodi sulla falsariga di parse1, parse2, ecc. Se parse1 non risultava in una risposta analizzata, veniva chiamato parse2, ecc. Questo non è necessariamente un best-practice, ma fa ciò che ti serve per .

+1

btw: nel caso non lo sapessi, hai implementato il pattern "Chain of Responsibility" con l'approccio parse1, parse2 ecc (non che sia una cosa cattiva o buona per questo problema, volevo solo menzionarlo. ..) –

+0

Ho usato per avere più metodi invece di un enorme switch in un ciclo. Tuttavia, l'enorme struttura dello switch si adatta meglio alle specifiche ed è più veloce quando il JIT esegue il kicking. – hsivonen

+0

Bene, si potrebbe sempre fare uno switch più piccolo() e il caso predefinito chiama un altro metodo con un piccolo switch e così via fino a quando tutti casi di interruttore sono coperti. Questo non sarebbe bello come una tabella di switch di grandi dimensioni, ma funzionerebbe. Nel mio caso non potrei usare switch dato che non funziona su Stringhe. – KitsuneYMG

0

Fa la differenza se non si compila con i simboli di debug (ad esempio il flag -g in javac)? Ciò potrebbe portare il metodo al di sotto del limite magico.

+0

Almeno nel compilatore Java Eclipse questo non ha alcun effetto sul numero di bytecode nel metodo compilato. (Suppongo che la tabella di debug sia separata.) – hsivonen

0

Non sarebbe possibile effettuare il refactoring del metodo in submethods? I moderni JIT sono in linea con quelle chiamate comunque.

+2

se non hai scritto a mano un parser prima, a volte lo scanner (lexer/tokenizer) può diventare grande, ma la leggibilità può andare giù quando lo dividi. ovviamente non ho visto il suo codice, ma ci sono stato ... –

+0

Ho già diviso le azioni effettive di tokenizer che possono essere suddivise senza influenzare il flusso di controllo. La stessa struttura di controllo è enorme. – hsivonen

+1

@ scott, ho scritto a mano un parser prima. Se è troppo complesso (per una ragione particolare) potrebbe essere un momento ragionevole per considerare un generatore di parser adatto. –

0

Se si tratta di un ciclo di tokenizer, sarebbe meglio farlo con un set di mappature di dati guidati e un po 'di riflessione, se necessario?

Così si memorizzano le corrispondenze token in una struttura che le associa ai dati sulla sintassi di tale token e ai metodi che implementano le funzioni associate. La ricerca può essere ottimizzata sulla struttura ed evitare il grande ciclo.

Questo introduce il problema di mantenere i dati e l'implementazione sincronizzati, ma è possibile generare i dati dal proprio codebase con un doclet o eventualmente un'annotazione.

Senza sapere esattamente cosa fa il tuo grande metodo, siamo limitati a cercare di ottimizzarlo nel modo che stai assumendo sia il migliore (e che a quanto pare non è possibile comunque).

+0

No, idealmente si desidera che le transizioni di stato compili fino a saltare nelle ricerche sulla struttura dei dati del codice e non. – hsivonen

+0

Sicuramente dipende piuttosto dal fatto che sia possibile ottimizzare la ricerca, sia attraverso una ricerca efficiente dello spazio di corrispondenza che attraverso la memorizzazione nella cache dei risultati. Una serie grande e codificata di token match potrebbe essere abbastanza resistente all'ottimizzazione (che presumo sia il motivo per cui stai cercando l'aiuto JIT). In un progetto precedente ho utilizzato i riferimenti al metodo con cache con risultati abbastanza buoni. Le modifiche apportate alla "sorgente" erano necessarie solo per invalidare il riferimento per proporre una nuova ricerca e l'esecuzione normale poteva essere eseguita a buona velocità, disattivando le chiamate al metodo senza alcun riscontro di prestazioni dalla corrispondenza di token. – AndyT

0

aumenta le prestazioni se si esegue un bytecode Shrinker/Obfuscator sulla classe? ad esempio, yguard, proguard, ...

forse puoi scrivere un postprocessore di file di classe usando asm perché il tuo caso d'uso è così specifico.

anche se rimuovi tutti i gotos inutili, ti porta sotto il limite magico?

0

Un elenco di bytecode libraries menziona BCEL e ASM, di cui avevo sentito parlare prima, insieme a molti altri che fanno varie cose.

Problemi correlati