I am fairly new to to Scala and am still trying to develop a feel for which approaches are efficient and which might contain hidden performance costs.
If I define a (non-tail) recursive function which contains an inner function. Are multiple copies of the inner function’s functional object instantiated for each recursive call?
For example in the following:
def sumDoubles(n: Int): Int = {
def dbl(a: Int) = 2 * a;
if(n > 0)
dbl(n) + sumDoubles(n - 1)
else
0
}
…how many copies of the dbl object exist on the stack for a call to sumDoubles(15)?
At the bytecode level
is exactly the same as
But don’t take my word for it
~/test$ javap -private -c Foo Compiled from "test.scala" public class Foo extends java.lang.Object implements scala.ScalaObject{ public Foo(); Code: 0: aload_0 1: invokespecial #10; //Method java/lang/Object."":()V 4: return private final int dbl$1(int); Code: 0: iconst_2 1: iload_1 2: imul 3: ireturn public int sumDoubles(int); Code: 0: iload_1 1: iconst_0 2: if_icmple 21 5: aload_0 6: iload_1 7: invokespecial #22; //Method dbl$1:(I)I 10: aload_0 11: iload_1 12: iconst_1 13: isub 14: invokevirtual #24; //Method sumDoubles:(I)I 17: iadd 18: goto 22 21: iconst_0 22: ireturn }If an inner function captures an immutable variable then there’s a translation. This code
Gets translated into
Again, the tools are there for you
~/test$ javap -private -c Foo Compiled from "test.scala" public class Foo extends java.lang.Object implements scala.ScalaObject{ public Foo(); Code: 0: aload_0 1: invokespecial #10; //Method java/lang/Object."":()V 4: return private final int dbl$1(int, int); Code: 0: iload_1 1: iload_2 2: imul 3: ireturn public int foo(int); Code: 0: iload_1 1: iconst_0 2: if_icmple 22 5: aload_0 6: iload_1 7: iload_1 8: invokespecial #23; //Method dbl$1:(II)I 11: aload_0 12: iload_1 13: iconst_1 14: isub 15: invokevirtual #25; //Method foo:(I)I 18: iadd 19: goto 23 22: iconst_0 23: ireturn }If mutable variable is captured then it has to be boxed which can be more expensive.
Gets translated into something like
~/test$ javap -private -c Foo Compiled from "test.scala" public class Foo extends java.lang.Object implements scala.ScalaObject{ public Foo(); Code: 0: aload_0 1: invokespecial #10; //Method java/lang/Object."":()V 4: return private final void subtract$1(scala.runtime.IntRef); Code: 0: aload_1 1: aload_1 2: getfield #18; //Field scala/runtime/IntRef.elem:I 5: iconst_1 6: isub 7: putfield #18; //Field scala/runtime/IntRef.elem:I 10: return public int bar(int); Code: 0: new #14; //class scala/runtime/IntRef 3: dup 4: iload_1 5: invokespecial #23; //Method scala/runtime/IntRef."":(I)V 8: astore_2 9: aload_2 10: getfield #18; //Field scala/runtime/IntRef.elem:I 13: iconst_0 14: if_icmple 29 17: aload_0 18: aload_2 19: invokespecial #27; //Method subtract$1:(Lscala/runtime/IntRef;)V 22: aload_2 23: getfield #18; //Field scala/runtime/IntRef.elem:I 26: goto 30 29: iconst_0 30: ireturn }Edit: adding first class functions
To get object allocations you need to use functions in a more first class manner
That desugars into something a bit like
Here’s the byte code
~/test$ javap -private -c Foo Compiled from "test.scala" public class Foo extends java.lang.Object implements scala.ScalaObject{ public Foo(); Code: 0: aload_0 1: invokespecial #10; //Method java/lang/Object."":()V 4: return public final int dbl$1(int); Code: 0: iconst_2 1: iload_1 2: imul 3: ireturn public int sumDoubles(int); Code: 0: aload_0 1: iload_1 2: new #20; //class Foo$$anonfun$sumDoubles$1 5: dup 6: aload_0 7: invokespecial #23; //Method Foo$$anonfun$sumDoubles$1."":(LFoo;)V 10: invokevirtual #29; //Method sumWithFunction:(ILscala/Function1;)I 13: ireturn public int sumWithFunction(int, scala.Function1); Code: 0: iload_1 1: iconst_0 2: if_icmple 30 5: aload_2 6: iload_1 7: invokestatic #36; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer; 10: invokeinterface #42, 2; //InterfaceMethod scala/Function1.apply:(Ljava/lang/Object;)Ljava/lang/Object; 15: invokestatic #46; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I 18: aload_0 19: iload_1 20: iconst_1 21: isub 22: aload_2 23: invokevirtual #29; //Method sumWithFunction:(ILscala/Function1;)I 26: iadd 27: goto 31 30: iconst_0 31: ireturn } ~/test$ javap -private -c "Foo\$\$anonfun\$sumDoubles\$1" Compiled from "test.scala" public final class Foo$$anonfun$sumDoubles$1 extends java.lang.Object implements scala.Function1,scala.ScalaObject,java.io.Serializable{ private final Foo $outer; public Foo$$anonfun$sumDoubles$1(Foo); Code: 0: aload_1 1: ifnonnull 12 4: new #10; //class java/lang/NullPointerException 7: dup 8: invokespecial #13; //Method java/lang/NullPointerException."":()V 11: athrow 12: aload_0 13: aload_1 14: putfield #17; //Field $outer:LFoo; 17: aload_0 18: invokespecial #20; //Method java/lang/Object."":()V 21: aload_0 22: invokestatic #26; //Method scala/Function1$class.$init$:(Lscala/Function1;)V 25: return public final java.lang.Object apply(java.lang.Object); Code: 0: aload_0 1: getfield #17; //Field $outer:LFoo; 4: astore_2 5: aload_0 6: aload_1 7: invokestatic #37; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I 10: invokevirtual #40; //Method apply:(I)I 13: invokestatic #44; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer; 16: areturn public final int apply(int); Code: 0: aload_0 1: getfield #17; //Field $outer:LFoo; 4: astore_2 5: aload_0 6: getfield #17; //Field $outer:LFoo; 9: iload_1 10: invokevirtual #51; //Method Foo.dbl$1:(I)I 13: ireturn public scala.Function1 andThen(scala.Function1); Code: 0: aload_0 1: aload_1 2: invokestatic #56; //Method scala/Function1$class.andThen:(Lscala/Function1;Lscala/Function1;)Lscala/Function1; 5: areturn public scala.Function1 compose(scala.Function1); Code: 0: aload_0 1: aload_1 2: invokestatic #60; //Method scala/Function1$class.compose:(Lscala/Function1;Lscala/Function1;)Lscala/Function1; 5: areturn public java.lang.String toString(); Code: 0: aload_0 1: invokestatic #65; //Method scala/Function1$class.toString:(Lscala/Function1;)Ljava/lang/String; 4: areturn }The anonymous class gets a lot of code copied in from the Function1 trait. That does have a cost in terms of class loading overhead, but doesn’t affect the cost of allocating the object or executing the code. The other cost is the boxing and unboxing of the integer. There’s hope that that cost will go away with 2.8’s @specialized annotation.