2013-06-08 15 views
35

Sto sostituendo alcuni componenti di generazione del codice in un programma Java con macro Scala e sto correndo nel limite della Java Virtual Machine sulla dimensione del codice byte generato per i singoli metodi (64 kilobyte).Scala macro e limite della dimensione del metodo JVM

Ad esempio, supponiamo di avere un file XML di grandi dimensioni che rappresenta un mapping da numeri interi a numeri interi che vogliamo utilizzare nel nostro programma. Vogliamo evitare il parsing questo file in fase di esecuzione, quindi dovremo scrivere una macro che farà il parsing in fase di compilazione e utilizzare il contenuto del file per creare il corpo del nostro metodo:

import scala.language.experimental.macros 
import scala.reflect.macros.Context 

object BigMethod { 
    // For this simplified example we'll just make some data up. 
    val mapping = List.tabulate(7000)(i => (i, i + 1)) 

    def lookup(i: Int): Int = macro lookup_impl 
    def lookup_impl(c: Context)(i: c.Expr[Int]): c.Expr[Int] = { 
    import c.universe._ 

    val switch = reify(new scala.annotation.switch).tree 
    val cases = mapping map { 
     case (k, v) => CaseDef(c.literal(k).tree, EmptyTree, c.literal(v).tree) 
    } 

    c.Expr(Match(Annotated(switch, i.tree), cases)) 
    } 
} 

In questo caso il metodo compilato sarebbe appena oltre il limite di dimensioni, ma invece di un errore piacevole che dice che, ci viene data una traccia di stack gigante con un sacco di chiamate a TreePrinter.printSeq e ci viene detto che abbiamo ucciso il compilatore.

Ho a solution che comporta la suddivisione dei casi in gruppi di dimensioni fisse, la creazione di un metodo separato per ciascun gruppo e l'aggiunta di una corrispondenza di primo livello che invia il valore di input al metodo del gruppo appropriato. Funziona, ma è sgradevole, e preferirei non dover usare questo approccio ogni volta che scrivo una macro in cui la dimensione del codice generato dipende da qualche risorsa esterna.

C'è un modo più pulito per affrontare questo problema? Ancora più importante, c'è un modo per affrontare questo tipo di errore del compilatore più facilmente? Non mi piace l'idea che un utente di una libreria ottenga un messaggio di errore "Quella voce sembra aver ucciso il compilatore" solo perché alcuni file XML che vengono elaborati da una macro hanno attraversato alcune (piuttosto basse) dimensioni della soglia.

+1

Questa domanda è stato contrassegnato come [ "già risposto"] (http://stackoverflow.com/q/6570343/334519), ma quello che sto chiedendo è completamente diverso da ciò che viene chiesto in che domanda.So che non è possibile modificare il limite di dimensioni del metodo della JVM: chiedo soluzioni alternative e la gestione degli errori nel contesto del nuovo sistema macro (2.10) di Scala. –

+0

Naively provato-ottimizzato e 2,11 appena seduto lì a riflettere. Perché il mio tempo su questa terra è finito, lo so. Forse diventerà ovvio per me perché questo è dovuto finire male. –

+0

@ som-snytt: Interessante, lo stesso qui, e non ho idea di cosa significhi. Senza '-optimize', 2.11.0-M3 fornisce un messaggio di errore ragionevole, almeno. –

risposta

4

Dato che qualcuno deve dire qualcosa, ho seguito le istruzioni allo Importers per provare a compilare l'albero prima di restituirlo.

Se si fornisce al compilatore un sacco di stack, verrà segnalato correttamente l'errore.

(non sembrava sapere cosa fare con l'annotazione interruttore, a sinistra come un futuro esercizio.)

[email protected]:~/tmp/bigmethod$ skalac bigmethod.scala ; skalac -J-Xss2m biguser.scala ; skala bigmethod.Test 
Error is java.lang.RuntimeException: Method code too large! 
Error is java.lang.RuntimeException: Method code too large! 
biguser.scala:5: error: You ask too much of me. 
    Console println s"5 => ${BigMethod.lookup(5)}" 
             ^
one error found 

al contrario di

[email protected]:~/tmp/bigmethod$ skalac -J-Xss1m biguser.scala 
Error is java.lang.StackOverflowError 
Error is java.lang.StackOverflowError 
biguser.scala:5: error: You ask too much of me. 
    Console println s"5 => ${BigMethod.lookup(5)}" 
             ^

in cui il codice del client è solo quello:

package bigmethod 

object Test extends App { 
    Console println s"5 => ${BigMethod.lookup(5)}" 
} 

La mia prima volta che utilizzo questa API, ma non l'ultima. Grazie per avermi dato un calcio d'inizio.

package bigmethod 

import scala.language.experimental.macros 
import scala.reflect.macros.Context 

object BigMethod { 
    // For this simplified example we'll just make some data up. 
    //final val size = 700 
    final val size = 7000 
    val mapping = List.tabulate(size)(i => (i, i + 1)) 

    def lookup(i: Int): Int = macro lookup_impl 
    def lookup_impl(c: Context)(i: c.Expr[Int]): c.Expr[Int] = { 

    def compilable[T](x: c.Expr[T]): Boolean = { 
     import scala.reflect.runtime.{ universe => ru } 
     import scala.tools.reflect._ 
     //val mirror = ru.runtimeMirror(c.libraryClassLoader) 
     val mirror = ru.runtimeMirror(getClass.getClassLoader) 
     val toolbox = mirror.mkToolBox() 
     val importer0 = ru.mkImporter(c.universe) 
     type ruImporter = ru.Importer { val from: c.universe.type } 
     val importer = importer0.asInstanceOf[ruImporter] 
     val imported = importer.importTree(x.tree) 
     val tree = toolbox.resetAllAttrs(imported.duplicate) 
     try { 
     toolbox.compile(tree) 
     true 
     } catch { 
     case t: Throwable => 
      Console println s"Error is $t" 
      false 
     } 
    } 
    import c.universe._ 

    val switch = reify(new scala.annotation.switch).tree 
    val cases = mapping map { 
     case (k, v) => CaseDef(c.literal(k).tree, EmptyTree, c.literal(v).tree) 
    } 

    //val res = c.Expr(Match(Annotated(switch, i.tree), cases)) 
    val res = c.Expr(Match(i.tree, cases)) 

    // before returning a potentially huge tree, try compiling it 
    //import scala.tools.reflect._ 
    //val x = c.Expr[Int](c.resetAllAttrs(res.tree.duplicate)) 
    //val y = c.eval(x) 
    if (!compilable(res)) c.abort(c.enclosingPosition, "You ask too much of me.") 

    res 
    } 
} 
10

Imo mettere i dati in .class non è proprio una buona idea. Anche loro sono analizzati, sono solo binari. Ma memorizzarli in JVM può avere un impatto negativo sulle prestazioni del raccoglitore di garbagge e del compilatore JIT.

Nella tua situazione, pre-compile l'XML in un file binario di formato corretto e analizzarlo. Formati idonei con strumenti esistenti possono essere ad es. FastRPC o buon vecchio DBF. Alcune implementazioni di quest'ultimo possono anche fornire indicizzazioni di base che potrebbero anche lasciare il parsing - l'app dovrebbe solo leggere dal rispettivo offset.

Problemi correlati