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?"
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With