2013-06-08 13 views
35

Zastępuję niektóre komponenty generowania kodu w programie Java za pomocą makr Scala i uruchamiam limit maszyny wirtualnej Java na rozmiar wygenerowanego kodu bajtu dla poszczególnych metod (64 kilobajtów).Makra Scala i limit rozmiaru metody JVM

Załóżmy na przykład, że mamy duży plik XML reprezentujący mapowanie z liczb całkowitych na liczby całkowite, które chcemy wykorzystać w naszym programie. Chcemy uniknąć analizowania tego pliku w czasie wykonywania, więc możemy napisać makro, które zrobi analizowania w czasie kompilacji i wykorzystać zawartość pliku do utworzenia ciała naszej metody:

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)) 
    } 
} 

W ten przypadku, gdy skompilowana metoda byłaby nieco większa od limitu rozmiaru, ale zamiast miłego błędu mówiącego, dostaliśmy gigantyczny ślad stosu z wieloma połączeniami do TreePrinter.printSeq i powiedziano nam, że zabiliśmy kompilator.

Mam a solution, który wymaga podziału skrzynek na grupy o ustalonej wielkości, utworzenia oddzielnej metody dla każdej grupy i dodania dopasowania na najwyższym poziomie, które wywołuje wartość wejściową do odpowiedniej metody grupy. Działa, ale jest nieprzyjemny i wolałbym nie używać tego podejścia za każdym razem, gdy piszę makro, w którym rozmiar wygenerowanego kodu zależy od jakiegoś zewnętrznego zasobu.

Czy istnieje lepszy sposób rozwiązania tego problemu? Co ważniejsze, czy istnieje sposób radzenia sobie z tego typu błędem kompilatora z większą wdziękiem? Nie podoba mi się pomysł, aby użytkownik biblioteki otrzymał niezrozumiały komunikat "Ten wpis najwyraźniej zabił kompilator" tylko z tego powodu, że jakiś plik XML, który jest przetwarzany przez makro, przekroczył pewien (dość niski) próg wielkości.

+1

To pytanie zostało oznaczone jako [ „już odpowiedział”] (http://stackoverflow.com/q/6570343/334519), ale co pytam jest zupełnie inny od tego, co jest proszony, że pytanie.Wiem, że nie można zmienić limitu rozmiaru metody JVM - pytam o obejścia i obsługę błędów w kontekście nowego systemu makro Scala (2.10). –

+0

Naiwnie wypróbowany - optymizm i 2.11 po prostu siedział tam dumny. Ponieważ mój czas na tej ziemi jest skończony, ctl-c'd. Może stanie się dla mnie jasne, dlaczego to musiało się źle skończyć. –

+0

@ som-snytt: Interesujące - to samo tutaj i nie ma pojęcia, co to oznacza. Bez '-optymalizacji', 2.11.0-M3 daje przynajmniej rozsądny komunikat o błędzie. –

Odpowiedz

4

Ponieważ ktoś musi coś powiedzieć, postępowałem zgodnie z instrukcjami pod Importers, aby spróbować skompilować drzewo przed zwróceniem go.

Jeśli podasz kompilatorowi dużo stosu, poprawnie zgłosi błąd.

(To nie wydaje się wiedzieć, co zrobić z adnotacją przełącznika lewej jako przyszłego ćwiczenia.)

[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 

w przeciwieństwie do

[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)}" 
             ^

gdzie kod klienta jest po prostu to:

package bigmethod 

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

Mój pierwszy raz korzystam z tego API, ale nie ostatniego. Dziękuję, że mnie przyspieszono.

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 umieszczanie danych w klasie .class nie jest dobrym pomysłem. Są one również przetwarzane, są tylko binarne. Ale przechowywanie ich w JVM może mieć negatywny wpływ na wydajność kolektora garbagge i kompilatora JIT.

W Twojej sytuacji skompilowałem plik XML do pliku binarnego o odpowiednim formacie i przeanalizowałem go. Kwalifikujące się formaty z istniejącym oprzyrządowaniem mogą być np. FastRPC lub dobry stary DBF. Niektóre implementacje tego ostatniego mogą również zapewnić podstawowe indeksowanie, które może nawet opuścić analizę - aplikacja po prostu odczytałaby dane z odpowiedniego offsetu.