【问题标题】:Scala macros and the JVM's method size limitScala 宏和 JVM 的方法大小限制
【发布时间】:2013-06-04 23:30:18
【问题描述】:

我正在用 Scala 宏替换 Java 程序中的一些代码生成组件,并且遇到了 Java 虚拟机对单个方法生成的字节码大小的限制(64 KB)。

例如,假设我们有一个很大的 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 文件已经超过了一些(相当低的)大小阈值。

【问题讨论】:

  • 这个问题已被标记为"already answered",但我所问的与该问题中所问的完全不同。我知道改变 JVM 的方法大小限制是不可能的——我询问的是 Scala 新 (2.10) 宏系统上下文中的变通方法和错误处理。
  • 天真地尝试了 -optimise 和 2.11 只是坐在那里沉思。因为我在这个地球上的时间是有限的,所以我 ctl-c'd。也许这对我来说会变得很明显,为什么这必须以糟糕的方式结束。
  • @som-snytt:有趣——这里也一样,但不知道这意味着什么。如果没有-optimize,至少 2.11.0-M3 确实给出了合理的错误消息。
  • Rex 对方法限制的一般解决方案不明智的评论或挑战:groups.google.com/forum/#!topic/scala-internals/f6PwUxc8K7I
  • 请注意,您的方法在 32kb 之后将不会被 JIT (HotSpot) 编译。根据 HotSpot 开发人员 Vladimir Ivanov 的 JIT 编译器概述。 Slides (English)Video (Russian)。滑动 N58。 Vladimir 提到“太大”意味着大约 32kb。

标签: java scala macros jvm scala-macros


【解决方案1】:

Imo 将数据放入 .class 并不是一个好主意。 它们也被解析,它们只是二进制的。但是将它们存储在 JVM 中可能会对垃圾收集器和 JIT 编译器的性能产生负面影响。

在您的情况下,我会将 XML 预编译成正确格式的二进制文件并对其进行解析。现有工具的合格格式可以是例如FastRPC 或好老 DBF。或者,如果您需要快速的高级查找和搜索,也可以预先填写 ElasticSearch 存储库。后者的一些实现还可以提供基本索引,甚至可以忽略解析 - 应用程序只会从相应的偏移量中读取。

【讨论】:

    【解决方案2】:

    由于有人不得不说些什么,我按照Importers 的说明在返回之前尝试编译树。

    如果你给编译器足够的堆栈,它会正确报告错误。

    (它似乎不知道如何处理 switch 注释,留作以后的练习。)

    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
    

    相对

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

    客户端代码就是这样:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-29
      • 1970-01-01
      • 2013-10-21
      • 1970-01-01
      • 2013-07-19
      • 1970-01-01
      • 2016-08-31
      • 2016-11-23
      相关资源
      最近更新 更多