2015-08-25 20 views
5

Ho un requisito insolito: la mia applicazione genera automaticamente il codice Java da uno script molto lungo (scritto in un linguaggio tipizzato dinamicamente). La sceneggiatura è così lunga che ho raggiunto lo the maximum method size of 65k of the JVM.Java - Come superare la dimensione massima del metodo nel codice generato automaticamente

Lo script consiste solo di semplici istruzioni sui tipi primitivi (nessuna chiamata ad altre funzioni oltre a quelle matematiche). Può sembrare:

... 
a = b * c + sin(d) 
... 
if a>10 then 
    e = a * 2 
else 
    e = a * abs(b) 
end 
... 

... che si trasforma come:

... 
double a = b * c + Math.sin(d); 
... 
double e; 
if(a>10){ 
    e = a * 2; 
}else{ 
    e = a * Math.abs(b); 
} 
... 


La mia prima idea per superare la limitazione metodo size è stato il seguente:

  • Girare tutto variabili locali nei campi
  • Dividere il codice ogni 100 righe (o più lungo se necessario in caso di un blocco if/else) in settembre metodi arati.

Qualcosa di simile:

class AutoGenerated { 

    double a,b,c,d,e,....; 

    void run1(){ 
     ... 
     a = b * c + sin(d); 
     ... 
     run2(); 
    } 

    void run2(){ 
     ... 
     if(a>10){ 
      e = a * 2; 
     }else{ 
      e = a * Math.abs(b); 
     } 
     ... 
     run3(); 
    } 

    ... 
} 

Sapete di qualsiasi altro modo che sarebbe più efficace? Si noti che ho bisogno che il codice venga eseguito il più velocemente possibile in quanto verrà eseguito in loop lunghi. Non posso ricorrere alla compilazione in C, poichè l'interoperabilità è anche un problema ...

Apprezzerei anche i puntatori alle librerie che potrebbero aiutarmi.

+0

Se si è preoccupati dell'efficienza, è necessario tenere presente che i metodi con dimensioni superiori a 8 KB non vengono compilati per impostazione predefinita. –

+0

Vorrei prendere in considerazione ciò che l'inlining può fare per te. Ci sono sequenze di codice ripetitive che, dopo l'inline, producono lo stesso codice? –

+0

@PeterLawrey, cosa succede poi oltre 8 KB? Il codice è interpretato, costa un sacco di efficienza? Cosa rende il compilatore decidere cosa dovrebbe compilare o no? Riguardo * inlining *, come funzionerebbe esattamente? Dovrei cercare "patterns" nel codice e creare metodi dedicati per gestirli? –

risposta

2

Stiamo utilizzando l'approccio simile in uno dei progetti nonostante i relativi svantaggi menzionati da altre persone. Chiamiamo i metodi multipli generati dal metodo di avvio singolo come suggerisce @ Marco13. In realtà calcoliamo (abbastanza precisamente) la dimensione del bytecode generato e iniziamo un nuovo metodo solo quando viene raggiunto il limite. Le nostre formule matematiche che traduciamo in codice Java sono disponibili come AstTree e abbiamo un visitatore speciale che conta la lunghezza del bytecode per ogni espressione. Per programmi così semplici è abbastanza stabile su versioni Java e diversi compilatori. Quindi non creiamo metodi più del necessario. Nel nostro caso è abbastanza difficile emettere il bytecode direttamente, ma puoi provare a farlo per la tua lingua usando ASM o una libreria simile (in questo modo, naturalmente, ASM calcolerà la lunghezza del bytecode per te).

Generalmente memorizziamo le variabili di dati in un unico array double[] (non sono necessari altri tipi) e lo passiamo come parametro. In questo modo non hai bisogno dell'enorme numero di campi (a volte abbiamo migliaia di variabili). D'altra parte l'accesso all'array locale può richiedere più bytecode byte rispetto all'accesso al campo per l'indice superiore a 127.

Un'altra preoccupazione è la dimensione del pool costante. Di solito abbiamo molte doppie costanti nel codice generato automaticamente. Se si dichiarano molti campi e/o metodi, i loro nomi prendono anche le voci costanti del pool. Quindi è possibile raggiungere il limite di piscina costante della classe. A volte lo colpiamo e generiamo classi annidate per superare questo problema.

Altre persone suggeriscono di modificare anche le opzioni JVM. Usa attentamente questi suggerimenti poiché essi avranno effetto non solo su questa classe generata automaticamente, ma anche su ogni altra classe (presumo che anche altri codici vengano eseguiti nella stessa JVM nel tuo caso).

0

Sarei tentato di scrivere un interprete o magari un compilatore in linea. Potresti anche ottenere dei guadagni di velocità perché la maggior parte della code base risultante più piccola si memorizza più facilmente nella cache.

+0

Intendi codificare un interprete (in Java) che interpreta la lingua originale? Come funziona un compilatore in-line e come si scrive uno? –

+0

@EricLeibenguth - Un compilatore in linea dovrebbe leggere la lingua e creare una struttura di dati che può essere eseguita direttamente, forse come una macchina di stato. Questo potrebbe essere più veloce di un interpeter. – OldCurmudgeon

+0

OK, capisco cosa intendi, ma mi sembra un po 'complesso costruirmi. Forse conosci una biblioteca che potrebbe aiutarti? –

0
  • Accendere tutte le variabili locali nei campi

che non avrà il minimo effetto. Dimensione del metodo == dimensione del codice. Niente a che vedere con le variabili locali, che influiscono solo sulla dimensione del frame di invocazione.

  • Split il codice ogni 100 righe (o più, se necessario in caso di un if/else blocco) in metodi separati.

Questa è la tua unica scelta, oltre a una strategia di implementazione completamente diversa.

Il problema con i generatori di codice è che generano codice.

+1

Trasformare le variabili locali in campi è stato un fattore abilitante per dividere il metodo (non devo capire quale metodo1 dovrebbe passare a method2, perché tutte le variabili sono disponibili come campi) –

1

Conversione variabili locali nei campi può effettivamente avere un impatto negativo sulle prestazioni finché il codice non è ottimizzato dal JIT (vedi this question and related ones per ulteriori informazioni). Ma vedo che, a seconda delle variabili che ciò comporta, potrebbero non esserci altre opzioni praticabili.


Ci possono essere limiti aggiuntivi per le dimensioni della compilazione e del metodo. Peter Lawrey ha menzionato nei commenti che "... i metodi di dimensioni superiori a 8 KB non sono compilati per impostazione predefinita" - Non ero a conoscenza di questo, ma di solito sa di cosa sta parlando, quindi dovresti scavare un po ' più profondo qui. Inoltre, potresti voler dare un'occhiata allo HotSpot VM options per vedere quali ulteriori limiti e impostazioni potrebbero essere rilevanti per te. Inizialmente pensavo che

-XX:MaxInlineSize=35: dimensione massima del bytecode di un metodo da inarcare.

potrebbe essere qualcosa da tenere a mente.

(In realtà, chiamando così molti metodi con una dimensione di MaxInlineSize che Inlining tutte queste chiamate superare la dimensione di 65K byte per il metodo contenente può essere un banco di prova ordinatamente entrata per la robustezza e le prove di casi bordo del processo di messa in linea ...)


È delineato uno schema di "telescopico" chiamata per i metodi:

void run1(){ 
    ... 
    run2(); 
} 

void run2(){ 
    ... 
    run3(); 
} 

Questo può portare anche a problemi: Considerando che hai> 650 di questi metodi (nel migliore dei casi), questo porterà almeno ad uno stack profondo molto e potrebbe in effetti causare uno StackOverflowError - di nuovo, a seconda su alcuni degli Memory Options. Potrebbe essere necessario aumentare le dimensioni dello stack impostando il parametro -Xss di conseguenza.


La descrizione attuale problema era un po 'vago, e senza ulteriori informazioni sul codice che deve essere generata (anche per quanto riguarda le domande circa ad esempio quanti variabili locali necessari, che potrebbero dover essere trasformato in variabili di istanza, ecc), mi piacerebbe proporre quanto segue:

  • creare molti piccoli metodi, se possibile (considerando la MaxInlineSize)
  • Prova a riutilizzo questi piccoli metodi (se tale riutilizzabilità può essere rilevato dall'ingresso con uno sforzo ragionevole)
  • chiamare questi metodi sequenzialmente, come in

    void run() 
    { 
        run0(); 
        run1(); 
        ... 
        run2000(); 
    } 
    

    per evitare problemi con la dimensione dello stack.


Tuttavia, se si è aggiunto ulteriori esempi o dettagli, si potrebbe probabilmente dare consigli più mirati. Questo potrebbe anche essere un esempio "completo" - non necessariamente coinvolgendo migliaia di righe di codici, ma mostrando i modelli attuali che appaiono lì.

+0

Grazie per la risposta, sicuramente aiuta! È difficile fornire un "campione rappresentativo" del codice, perché è potenzialmente piuttosto diversificato. Ci sono * alcuni * pattern ricorrenti a livello di istruzioni (varie linee si assomigliano), ma non tanto a livello di programma (blocchi di linee che si assomigliano). –