A Scala example using method calls:
#Inc.scala
object Inc {
def main(args: Array[String]):Unit =
{
var i:Int=0
for(j <- (1 to 1000000000)) // Scala!, Y U No Project Coin? ;)
{
i = inc(i)
}
println(i);
}
def inc(i:Int):Int=i+1
}
object Inc {
def main(args: Array[String]):Unit =
{
var i:Int=0
for(j <- (1 to 1000000000)) // Scala!, Y U No Project Coin? ;)
{
i = inc(i)
}
println(i);
}
def inc(i:Int):Int=i+1
}
The same code using tail recursion (@tailrec)
#TailRec.scala
import scala.annotation.tailrec
object TailRec {
def main(args: Array[String]):Unit =
{
var i = inc(0, 1000000000)
println(i);
}
@tailrec
def inc(i:Int, iter:Int):
Int=if (iter>0)
inc(i+1, iter-1)
else i
}
import scala.annotation.tailrec
object TailRec {
def main(args: Array[String]):Unit =
{
var i = inc(0, 1000000000)
println(i);
}
@tailrec
def inc(i:Int, iter:Int):
Int=if (iter>0)
inc(i+1, iter-1)
else i
}
Compile them both using scalac:
scalac Inc.scala
scalac TailRec.scala
scalac TailRec.scala
Time the runs (obviously would be better with JMH!):
time ./bin/scala Inc
1000000000
real 0m4.461s
user 0m4.220s
sys 0m0.184s
time ./bin/scala TailRec
1000000000
real 0m1.558s
user 0m1.540s
sys 0m0.140s
1000000000
real 0m4.461s
user 0m4.220s
sys 0m0.184s
time ./bin/scala TailRec
1000000000
real 0m1.558s
user 0m1.540s
sys 0m0.140s
Hmmm, looks like inlining that method took a while to kick in!
How about with no inlining?
time ./bin/scala Inc -J-XX:-Inline
1000000000
real 0m28.890s
user 0m28.790s
sys 0m0.120s
time ./bin/scala TailRec -J-XX:-Inline
1000000000
real 0m1.534s
user 0m1.532s
sys 0m0.096s
1000000000
real 0m28.890s
user 0m28.790s
sys 0m0.120s
time ./bin/scala TailRec -J-XX:-Inline
1000000000
real 0m1.534s
user 0m1.532s
sys 0m0.096s
Ouch! Remember kids, inlining is your friend. Keep those methods small ;)
Here's the bytecode for the inc() method:
#Inc
public int inc(int);
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: iload_1
1: iconst_1
2: iadd
3: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this LInc$;
0 4 1 i I
LineNumberTable:
line 14: 0
public int inc(int);
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: iload_1
1: iconst_1
2: iadd
3: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this LInc$;
0 4 1 i I
LineNumberTable:
line 14: 0
And here it is for the tailrec version
#TailRec
public int inc(int, int);
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=3
0: iload_2
1: iconst_0
2: if_icmple 16
5: iload_1
6: iconst_1
7: iadd
8: iload_2
9: iconst_1
10: isub
11: istore_2
12: istore_1
13: goto 0
16: iload_1
17: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 this LTailRec$;
0 18 1 i I
0 18 2 iter I
LineNumberTable:
line 13: 0
line 14: 5
line 15: 16
line 12: 17
StackMapTable: number_of_entries = 2
frame_type = 0 /* same */
frame_type = 15 /* same */
public int inc(int, int);
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=3
0: iload_2
1: iconst_0
2: if_icmple 16
5: iload_1
6: iconst_1
7: iadd
8: iload_2
9: iconst_1
10: isub
11: istore_2
12: istore_1
13: goto 0
16: iload_1
17: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 this LTailRec$;
0 18 1 i I
0 18 2 iter I
LineNumberTable:
line 13: 0
line 14: 5
line 15: 16
line 12: 17
StackMapTable: number_of_entries = 2
frame_type = 0 /* same */
frame_type = 15 /* same */
JITWatch inspection of Inc with inlining:
JITWatch inspection of Inc without inlining:
With thanks to Ignasi Marimon-Clos (@ignasi35) for the Scala help and to Jean-Philippe Bempel (@jpbempel) for pointing out -XX:-Inline :)
For expression are adding a lot of overhead, try the following: