【发布时间】:2011-02-14 03:43:09
【问题描述】:
在对this question 的公认最佳回复中,有一个明确的解释为什么会发生拳击。
但是,如果我反编译代码(使用 java 反编译器),我看不到 scala.runtime.BoxesRunTime 的使用。此外,如果我分析代码(使用 JProfiler),我看不到任何 BoxesRunTime 实例。
那么,我如何真正看到装箱/拆箱的证据?
【问题讨论】:
在对this question 的公认最佳回复中,有一个明确的解释为什么会发生拳击。
但是,如果我反编译代码(使用 java 反编译器),我看不到 scala.runtime.BoxesRunTime 的使用。此外,如果我分析代码(使用 JProfiler),我看不到任何 BoxesRunTime 实例。
那么,我如何真正看到装箱/拆箱的证据?
【问题讨论】:
在这段代码中:
class Foo[T] {
def bar(i: T) = i
}
object Main {
def main(args: Array[String]) {
val f = new Foo[Int]
f.bar(5)
}
}
bar 的调用应该首先将整数装箱。使用 Scala 2.8.1 编译并使用:
javap -c -l -private -verbose -classpath <dir> Main$
查看为Main 类的main 方法生成的字节码会产生:
public void main(java.lang.String[]);
...
9: iconst_5
10: invokestatic #24; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
13: invokevirtual #28; //Method Foo.bar:(Ljava/lang/Object;)Ljava/lang/Object;
16: pop
17: return
...
您可以在调用bar 之前看到对BoxesRunTime 的调用。
BoxesRunTime 是一个包含原始类型装箱方法的对象,因此总共应该只有一个实例。这里的诀窍是库中的这个特定文件是用 Java 编写的,并且转换是静态方法。出于这个原因,在运行时没有它的任何实例,尽管在 Scala 代码中使用它就像是一个对象。
您可能应该使用 JProfile 寻找装箱的原语(例如 java.lang.Integer),尽管我不确定 JVM 是如何工作的,以及它是否真的可以在运行时重写代码并对其进行优化以避免装箱。据我所知,它不应该应用专业化(但我相信 CLR 可以)。一些带有和不带有装箱情况的微基准测试是了解运行时发生了什么的另一种方法。
编辑:
以上假设类型参数未使用@specialized 注释进行注释。在这种情况下,可以避免装箱/拆箱。标准库中的某些类是专门的。见this sid。
【讨论】:
给定以下 Test.scala 程序:
object Test {
def main(args:Array[String]) {
val list = List(1,5,15)
val res = list.map(e => e*2).filter(e => e>10)
}
}
如果我用 scalac -Xprint:jvm Test.scala 编译,我会得到这个 sn-p,表明发生了专业化(抱歉,宽粘贴):
package <empty> {
final class Test extends java.lang.Object with ScalaObject {
def main(args: Array[java.lang.String]): Unit = {
val list: List = immutable.this.List.apply(scala.this.Predef.wrapIntArray(Array[Int]{1, 5, 15}));
val res: List = list.map({
(new Test$$anonfun$1(): Function1)
}, immutable.this.List.canBuildFrom()).$asInstanceOf[scala.collection.TraversableLike]().filter({
(new Test$$anonfun$2(): Function1)
}).$asInstanceOf[List]();
()
};
def this(): object Test = {
Test.super.this();
()
}
};
@SerialVersionUID(0) @serializable final <synthetic> class Test$$anonfun$1 extends scala.runtime.AbstractFunction1$mcII$sp {
final def apply(e: Int): Int = Test$$anonfun$1.this.apply$mcII$sp(e);
<specialized> def apply$mcII$sp(v1: Int): Int = v1.*(2);
final <bridge> def apply(v1: java.lang.Object): java.lang.Object = scala.Int.box(Test$$anonfun$1.this.apply(scala.Int.unbox(v1)));
def this(): Test$$anonfun$1 = {
Test$$anonfun$1.super.this();
()
}
};
@SerialVersionUID(0) @serializable final <synthetic> class Test$$anonfun$2 extends scala.runtime.AbstractFunction1$mcZI$sp {
final def apply(e: Int): Boolean = Test$$anonfun$2.this.apply$mcZI$sp(e);
<specialized> def apply$mcZI$sp(v1: Int): Boolean = v1.>(10);
final <bridge> def apply(v1: java.lang.Object): java.lang.Object = scala.Boolean.box(Test$$anonfun$2.this.apply(scala.Int.unbox(v1)));
def this(): Test$$anonfun$2 = {
Test$$anonfun$2.super.this();
()
}
}
}
这可能是您在字节码中看不到任何装箱证据的原因...
【讨论】:
Test$$anonfun$# 发生特化,因为函数是特化的。但是,这些函数匿名类在map 和filter 方法中使用,这些方法不是专门的,它们调用它们的默认泛型apply,它需要一个对象——装箱应该在那里发生。如果你反编译TraversableLike的代码,并搜索map或filter,它们应该有对BoxesRunTime的调用。