java - descargar - Las macros de Scala y el límite de tamaño del método de JVM
scala jdk (2)
Como alguien tiene que decir algo, seguí las instrucciones en Importers
para intentar compilar el árbol antes de devolverlo.
Si le da al compilador mucha pila, informará correctamente el error.
(No parecía saber qué hacer con la anotación de cambio, queda como un ejercicio futuro).
apm@mara:~/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
Opuesto a
apm@mara:~/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)}"
^
donde el código del cliente es solo eso:
package bigmethod
object Test extends App {
Console println s"5 => ${BigMethod.lookup(5)}"
}
La primera vez que uso esta API, pero no la última. Gracias por ayudarme a comenzar.
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
}
}
Estoy reemplazando algunos componentes de generación de código en un programa Java con macros de Scala, y me estoy ejecutando en el límite de la Máquina Virtual de Java en el tamaño del código de bytes generado para métodos individuales (64 kilobytes).
Por ejemplo, supongamos que tenemos un archivo XML de gran tamaño que representa una asignación de enteros a enteros que queremos usar en nuestro programa. Queremos evitar el análisis de este archivo en tiempo de ejecución, por lo que escribiremos una macro que realizará el análisis en tiempo de compilación y usará el contenido del archivo para crear el cuerpo de nuestro método:
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))
}
}
En este caso, el método compilado estaría un poco por encima del límite de tamaño, pero en lugar de un buen error al decir eso, recibimos un rastro de pila gigante con muchas llamadas a TreePrinter.printSeq
y nos dicen que hemos matado al compilador .
Tengo una solución que implica dividir los casos en grupos de tamaño fijo, crear un método separado para cada grupo y agregar una coincidencia de nivel superior que distribuya el valor de entrada al método del grupo apropiado. Funciona, pero es desagradable, y preferiría no tener que usar este enfoque cada vez que escribo una macro donde el tamaño del código generado depende de algún recurso externo.
¿Hay alguna manera más clara de abordar este problema? Más importante aún, ¿hay alguna forma de lidiar con este tipo de error de compilación de forma más elegante? No me gusta la idea de que un usuario de la biblioteca obtenga un mensaje de error "Ese mensaje parece haber matado al compilador" ininteligible solo porque algún archivo XML que está siendo procesado por una macro ha cruzado un umbral de tamaño (bastante bajo).
Imo poner datos en .class no es realmente una buena idea. También se analizan, solo son binarios. Pero almacenarlos en JVM puede tener un impacto negativo en el rendimiento del recopilador Garbagge y el compilador JIT.
En su situación, yo precompilaría el XML en un archivo binario de formato adecuado y lo analizaría. Los formatos elegibles con herramientas existentes pueden ser, por ejemplo, FastRPC o un buen DBF antiguo. Algunas implementaciones de este último también pueden proporcionar indexación básica que incluso podría dejar el análisis sintáctico: la aplicación solo leería desde el desplazamiento respectivo.