2013-06-08 5 views
35

Я заменяю некоторые компоненты генерации кода в программе Java с помощью макросов Scala и выполняю ограничения на виртуальную машину Java по размеру сгенерированного байтового кода для отдельных методов (64 килобайта).Макросы Scala и ограничение размера метода JVM

Например, предположим, что у нас есть XML-файл большого размера, который представляет собой сопоставление целых чисел с целыми числами, которые мы хотим использовать в нашей программе. Мы хотим, чтобы избежать разбора этого файла во время выполнения, поэтому мы напишем макрос, который будет делать синтаксический разбор во время компиляции и использовать содержимое файла, чтобы создать тело нашего метода:

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

В этом в случае, если скомпилированный метод будет находиться чуть выше предела размера, но вместо хорошей ошибки, говорящей о том, что нам дается гигантская трассировка стека с большим количеством вызовов до TreePrinter.printSeq и им сообщается, что мы убили компилятор.

У меня есть a solution, который включает разбиение случаев на группы фиксированного размера, создание отдельного метода для каждой группы и добавление соответствия верхнего уровня, которое отправляет входное значение методу соответствующей группы. Он работает, но это неприятно, и я бы предпочел не использовать этот подход каждый раз, когда пишу макрос, где размер сгенерированного кода зависит от некоторого внешнего ресурса.

Есть ли более чистый способ решить эту проблему? Что еще более важно, есть ли способ более грамотно справиться с такой ошибкой компилятора? Мне не нравится идея того, что пользователь библиотеки получает сообщение об ошибке «Эта запись, похоже, убил компилятор», только потому, что некоторый XML-файл, обрабатываемый макросом, пересек некоторое (довольно низкое) размерное значение.

+1

Этот вопрос был отмечен как [ «уже ответили»] (http://stackoverflow.com/q/6570343/334519), но то, что я спрашиваю, совершенно отличается от того, что спрашивают, что вопрос.Я знаю, что невозможно изменить ограничение размера метода JVM - я спрашиваю об обходных решениях и обработке ошибок в контексте новой (2.10) макроса Scala. –

+0

Наивно попробовал -оптимизировать и 2.11 просто сидел там, размышляя. Поскольку мое время на этой земле конечно, я ctl-c'd. Может быть, мне станет очевидно, почему это должно было закончиться плохо. –

+0

@ som-snytt: Интересно то же самое, и не знаю, что это значит. Без '-optimize', 2.11.0-M3 действительно дает разумное сообщение об ошибке, по крайней мере. –

ответ

4

Поскольку кто-то должен что-то сказать, я выполнил инструкции в Importers, чтобы попытаться скомпилировать дерево, прежде чем возвращать его.

Если вы предоставите компилятору много стека, он правильно сообщит об ошибке.

(Это, похоже, не знает, что делать с выключателем аннотацию, слева в качестве будущего упражнения.)

[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 

в отличие от

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

где код клиента просто что:

package bigmethod 

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

Мой первый раз, используя этот API, но не мой последний. Спасибо, что запустили меня.

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, вводящий данные в класс. На самом деле это не очень хорошая идея. Они также разбираются, они просто двоичные. Но их хранение в JVM может отрицательно сказаться на производительности сборщика garbagge и JIT-компилятора.

В вашей ситуации я бы предварительно скомпилировал XML в двоичный файл соответствующего формата и проанализировал его. Разрешенные форматы с существующим инструментом могут быть, например, FastRPC или старый старый DBF. Некоторые реализации последних могут также обеспечивать базовое индексирование, которое могло бы даже оставить разбор - приложение просто прочитало бы из соответствующего смещения.