How to spot boxing/unboxing in Scala
Asked Answered
H

2

12

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?"

Honkytonk answered 27/6, 2011 at 14:59 Comment(0)
E
6

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.

Ezmeralda answered 27/6, 2011 at 15:56 Comment(1)
How did you know for sure that HotSpot was doing that?Honkytonk
T
2

You can use a profiler (jvisualvm comes free with Oracle SDK) to look for boxing/unboxing. The advantage is that you will only spot relevant boxing - nothing that happens only a few times and nothing that gets optimized away by HotSpot.

Well, I'm not totally sure about the last one but I expect it to be more probably that JIT is still used when using a sampling profiler (which jvisualvm also provides).

Trapeziform answered 27/6, 2011 at 18:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.