2013-06-08 5 views
35

Javaプログラムのコード生成コンポーネントの一部をScalaマクロに置き換え、Java仮想マシンの個々のメソッドの生成バイトコードのサイズ制限(64キロバイト)。ScalaマクロとJVMのメソッドサイズ制限

たとえば、私たちのプログラムで使用したい整数から整数へのマッピングを表すlarge-ish 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のメソッドのサイズ制限を変更することはできないことを知っています.Scalaの新しい(2.10)マクロシステムのコンテキストでは、回避策とエラー処理について質問しています。 –

+0

素朴に試してみました。この地球上の私の時間は有限であるので、私は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

。データを.classに入れることは、本当に良い考えではありません。 これらも解析され、バイナリです。しかし、それらをJVMに格納すると、garbaggeコレクタとJITコンパイラのパフォーマンスに悪影響が及ぶ可能性があります。

あなたの状況では、XMLを適切な形式のバイナリファイルにあらかじめコンパイルして解析します。既存のツーリングを備えた適切なフォーマットは、例えば、 または良い古いDBF。後者のいくつかの実装では、基本的なインデックス作成機能も提供されています。これにより、解析が終了しても、アプリケーションはそれぞれのオフセットから読み込みます。

関連する問題