Why does this Scala code:
class Test
{
def foo: (Int, String) =
{
(123, "123")
}
def bar: Unit =
{
val (i, s) = foo
}
}
generate the following bytecode for bar()
that constructs a new Tuple2
, passes the Tuple2
from foo()
to it and then gets the values out of it?
public void bar();
Code:
0: aload_0
1: invokevirtual #28; //Method foo:()Lscala/Tuple2;
4: astore_2
5: aload_2
6: ifnull 40
9: new #7; //class scala/Tuple2
12: dup
13: aload_2
14: invokevirtual #32; //Method scala/Tuple2._1:()Ljava/lang/Object;
17: aload_2
18: invokevirtual #35; //Method scala/Tuple2._2:()Ljava/lang/Object;
21: invokespecial #20; //Method scala/Tuple2."<init>":(Ljava/lang/Object;Ljava/lang/Object;)V
24: astore_1
25: aload_1
26: invokevirtual #39; //Method scala/Tuple2._1$mcI$sp:()I
29: istore_3
30: aload_1
31: invokevirtual #35; //Method scala/Tuple2._2:()Ljava/lang/Object;
34: checkcast #41; //class java/lang/String
37: astore 4
Is this because the compiler isn't checking that foo()
s return value isn't a tuple?
Will the JVM optimize the construction away anyway?
This seems to be according to the spec (in 4.1 Value Declarations and Definitions - slightly reformatted for stackoverflow display):
Value definitions can alternatively have a pattern (§8.1) as left-hand side. If p is some pattern other than a simple name or a name followed by a colon and a type, then the value definition
val p = e
is expanded as follows:
- If the pattern
p
has bound variablesx1, . . . , xn
, wheren >= 1
: Here,$x
is a fresh name.
val $x = e match {case p => (x1, . . . , xn)}
val x1 = $x._1
. . .
val xn = $x._n
So the tuple creation happens in the parser phase. So val (i, s) = (1, "s")
expends at the end of the parser phase to:
private[this] val x$1 = scala.Tuple2(1, "s"): @scala.unchecked match {
case scala.Tuple2((i @ _), (s @ _)) => scala.Tuple2(i, s)
};
val i = x$1._1;
val s = x$1._2
Measuring this on this simple test on a million iterations:
def foo: (Int, String) = (123, "123")
def bar: Unit = { val (i, s) = foo }
def bam: Unit = { val f = foo; val i = f._1; val s = f._2 }
yields
foo: Elapsed: 0.030
bar: Elapsed: 0.051
._1 ._2 access: Elapsed: 0.040
and with -optimize flag:
foo: Elapsed: 0.027
bar: Elapsed: 0.049
._1 ._2 access: Elapsed: 0.029
It seems like a missed optimization opportunity in scalac.
The relevant part of the compiler us Unapplies#caseClassUnapplyReturnValue, which calls TreeDSL#SOME
to generate code to create a new TupleN
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