36 votes

Macros Scala et limite de taille de méthode de la machine virtuelle Java

Je suis le remplacement de certains composants de génération de code dans un programme Java avec Scala macros, et je suis en cours d'exécution dans la Machine Virtuelle Java de limite sur la taille de l'généré octets de code pour les méthodes individuelles (64 kilo-octets).

Par exemple, supposons que nous avons un grand-ish fichier XML qui représente une cartographie à partir des entiers les nombres entiers que nous voulons utiliser dans notre programme. Nous voulons éviter de parser ce fichier au moment de l'exécution, de sorte que nous allons écrire une macro qui permet de faire l'analyse au moment de la compilation et de l'utilisation du contenu du fichier pour créer le corps de notre méthode:

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

Dans ce cas, la compilation de la méthode juste au-dessus de la limite de taille, mais au lieu d'un joli message d'erreur disant que, nous nous sommes donné un géant de la trace de la pile avec beaucoup d'appels à l' TreePrinter.printSeq et dit que nous avons tué le compilateur.

J'ai une solution qui consiste à fendre le cas en fixe la taille des groupes, de créer une méthode distincte pour chaque groupe, et l'ajout d'un niveau supérieur de match que les dépêches de la valeur d'entrée dans le groupe approprié de la méthode. Il fonctionne, mais c'est désagréable, et je préférerais ne pas avoir à utiliser cette approche à chaque fois que j'écris une macro où la taille du code généré dépend de certaines ressources externes.

Est-il le moyen le plus propre pour s'attaquer à ce problème? Plus important encore, il est un moyen de traiter avec ce genre d'erreur du compilateur, avec plus de grâce? Je n'aime pas l'idée d'une bibliothèque de l'utilisateur de contracter une incompréhensible "Que l'entrée semble avoir tué le compilateur" message d'erreur juste parce que certains fichier XML en cours de traitement par une macro a traversé quelques-uns (assez faible) de la taille de seuil.

10voto

Ondra Žižka Points 8262

Omi mettre des données .la classe n'est pas vraiment une bonne idée. Ils sont analysés en tant que bien, ils sont juste binaires. Mais de les ranger dans la JVM peut avoir un impact négatif sur les performances de la garbagge collecteur et le compilateur JIT.

Dans votre situation, j'aurais pré-compiler les données XML dans un fichier binaire de format approprié et d'analyser cela. Éligibles formats avec des outils existants peuvent être par exemple des FastRPC ou bon vieux DBF. Certaines implémentations de ce dernier peut également fournir une base d'indexation qui pourrait même laisser l'analyse de l'application serait de juste lire le décalage.

4voto

som-snytt Points 17224

Depuis que quelqu'un a à dire quelque chose, j'ai suivi les instructions à l' Importers d'essayer de compiler l'arbre avant de le retourner.

Si vous donner le compilateur beaucoup de pile, il sera correctement signaler l'erreur.

(Il n'a pas l'air de savoir quoi faire avec le commutateur d'annotation, de gauche comme un exercice futur.)

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

par opposition à

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

où le code du client est juste que:

package bigmethod

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

Ma première fois en utilisant cette API, mais pas mon dernier. Merci de m'avoir lancé.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X