Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to spot boxing/unboxing in Scala

Following a suggestion by extempore recently about how to get scala to tell me whether there was boxing going on by looking at the bytecode, I created this class:

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

Had a look at the bytecode for 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

No signs of box/unbox there. But I'm still suspicious, so I compiled it with -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();
      ()
    }
  }
}

The main observations about this code are that the created anonymous function has been specialized for Long => Double and that the map functionality is provided by longArrayOps(ls).map (ArrayOps is not specialized).

The question is: "is boxing/unboxing occurring in this example?"

like image 262
oxbow_lakes Avatar asked Jun 27 '11 14:06

oxbow_lakes


1 Answers

There probably is boxing going on. You have to look at position where the actual map-call is made:

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

At runtime this should go to ArrayOps#ofLong. Since ArrayOps is not specialized it's unlikely that your map-call get's through without boxing.

To find out for sure you can either try to follow the calls through the bytecode (may be difficult because as in your function you may encounter runtime dispatch) or step through with a debugger (difficult because most debuggers don't show the bytecode, however you may be able to set breakpoints to Scala's or Java's boxing methods).

For practical reasons we found that it may be irrelevant if boxing is done in bytecode because for hot methods the Hotspot compiler sometimes manages to completely inline the standard higher order function call chain in which case boxing can be eliminated.

like image 111
jrudolph Avatar answered Sep 18 '22 22:09

jrudolph