12

根据最近extempore关于如何让 scala 通过查看字节码来告诉我是否正在进行装箱的建议,我创建了这个类:

class X { def foo(ls : Array[Long]) = ls map (_.toDouble)

查看了以下字节码foo

public double[] foo(long[]);
  Code:
   Stack=4, Locals=2, Args_size=2
   0:   getstatic       #11; //Field scala/Predef$.MODULE$:Lscala/Predef$;
   3:   aload_1
   4:   invokevirtual   #16; //Method scala/Predef$.longArrayOps:([J)Lscala/collection/mutable/ArrayOps;
   7:   new     #18; //class X$$anonfun$foo$1
   10:  dup
   11:  aload_0
   12:  invokespecial   #22; //Method X$$anonfun$foo$1."<init>":(LX;)V
   15:  getstatic       #27; //Field scala/Array$.MODULE$:Lscala/Array$;
   18:  getstatic       #32; //Field scala/reflect/Manifest$.MODULE$:Lscala/reflect/Manifest$;
   21:  invokevirtual   #36; //Method scala/reflect/Manifest$.Double:()Lscala/reflect/AnyValManifest;
   24:  invokevirtual   #40; //Method scala/Array$.canBuildFrom:(Lscala/reflect/ClassManifest;)Lscala/collection/generic/CanBuildFrom;
   27:  invokeinterface #46,  3; //InterfaceMethod scala/collection/TraversableLike.map:(Lscala/Function1;Lscala/collection/generic/CanBuildFrom;)Ljava/lan                                   g/Object;
   32:  checkcast       #48; //class "[D"
   35:  areturn
  LineNumberTable:
   line 7: 0

那里没有装箱/拆箱的迹象。但是我还是很疑惑,所以用-print()编译了一下:

[[syntax trees at end of cleanup]]// Scala source: X.scala
package <empty> {
  class X extends java.lang.Object with ScalaObject {
    def foo(ls: Array[Long]): Array[Double] = scala.this.Predef.longArrayOps(ls).map({
(new anonymous class X$$anonfun$foo$1(X.this): Function1)
}, scala.this.Array.canBuildFrom(reflect.this.Manifest.Double())).$asInstanceOf[Array[Double]]();
  def this(): X = {
    X.super.this();
    ()
  }
};
@SerialVersionUID(0) final <synthetic> class X$$anonfun$foo$1 extends scala.runtime.AbstractFunction1$mcDJ$sp with Serializable {
  final def apply(x$1: Long): Double = X$$anonfun$foo$1.this.apply$mcDJ$sp(x$1);
  <specialized> def apply$mcDJ$sp(v1: Long): Double = v1.toDouble();
  final <bridge> def apply(v1: java.lang.Object): java.lang.Object = scala.Double.box(X$$anonfun$foo$1.this.apply(scala.Long.unbox(v1)));
    def this($outer: X): anonymous class X$$anonfun$foo$1 = {
      X$$anonfun$foo$1.super.this();
      ()
    }
  }
}

关于这段代码的主要观察是,创建的匿名函数已被专门化,Long => Double并且该map功能由longArrayOps(ls).map( ArrayOpsis not dedicated ) 提供。

问题是:“在这个例子中是否发生了装箱/拆箱?”

4

2 回答 2

6

可能正在进行拳击比赛。您必须查看进行实际map调用的位置:

27:  invokeinterface #46,  3; //InterfaceMethod scala/collection/TraversableLike.map:(Lscala/Function1;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;

在运行时,这应该转到ArrayOps#ofLong. 由于ArrayOps不是专门的,因此您的map-call 不太可能在没有拳击的情况下通过。

要确定您可以尝试通过字节码跟踪调用(可能很困难,因为在您的函数中您可能会遇到运行时调度)或使用调试器单步执行(很困难,因为大多数调试器不显示字节码,但是您可以为 Scala 或 Java 的装箱方法设置断点)。

出于实际原因,我们发现如果在字节码中进行装箱可能无关紧要,因为对于热方法,热点编译器有时会设法完全内联标准的高阶函数调用链,在这种情况下可以消除装箱。

于 2011-06-27T15:56:59.077 回答
2

您可以使用分析器(jvisualvm 随 Oracle SDK 免费提供)来查找装箱/拆箱。优点是您只会发现相关的拳击 - 不会只发生几次,也不会被 HotSpot 优化掉。

好吧,我不完全确定最后一个,但我希望在使用采样分析器(jvisualvm 也提供)时仍然使用 JIT 的可能性更大。

于 2011-06-27T18:02:30.367 回答