Closed
Description
Compiler version
Scala3: 3.0.0, 3.0.1
Scala2: 2.13.0
JVM: openjdk 11.0.9.1
Scalameter: 0.21
Minimized code
package scalashop
import org.scalameter.{Key, Warmer, config}
object TestDoubleLoopPerformance {
val standardConfig = config(
Key.exec.minWarmupRuns := 5,
Key.exec.maxWarmupRuns := 10,
Key.exec.benchRuns := 10,
// Key.verbose := true
) withWarmer(Warmer.Default())
def main(args: Array[String]): Unit = {
val whileLoopTime = standardConfig measure {
for (iter <- 0 to 100) {
sumWithWhileLoop(1000, 2000)
}
}
println(s"while loop time: $whileLoopTime")
val rangeLoopTime = standardConfig measure {
for (iter <- 0 to 100) {
sumWithRange(1000, 2000)
}
}
println(s"range loop time: $rangeLoopTime")
}
private def sumWithRange(xEnd: Int, yEnd: Int) = {
var sum = 0
for {
x <- 0 until xEnd
y <- 0 until yEnd
} sum += 1
sum
}
private def sumWithWhileLoop(xEnd: Int, yEnd: Int) = {
var sum = 0
var x = 0
while (x < xEnd) {
var y = 0
while (y < yEnd) {
sum += 1
y += 1
}
x += 1
}
sum
}
}
Output
Scala2:
while loop time: 0.015882699999999996 ms
range loop time: 0.20659249999999996 ms
Scala3:
while loop time: 0.011921900000000001 ms
range loop time: 464.97765689999994 ms
while loop benchmark is roughly comparable, there is a massive downgrade in performance for Range.
I decompiled the code using IntelliJ
Scala2:
private int sumWithRange(final int xEnd, final int yEnd) {
IntRef sum = IntRef.create(0);
scala.runtime.RichInt..MODULE$.until$extension(scala.Predef..MODULE$.intWrapper(0), xEnd).foreach$mVc$sp((x) -> {
scala.runtime.RichInt..MODULE$.until$extension(scala.Predef..MODULE$.intWrapper(0), yEnd).foreach$mVc$sp((y) -> {
++sum.elem;
});
});
return sum.elem;
}
Scala3:
private int sumWithRange(final int xEnd, final int yEnd) {
IntRef sum = IntRef.create(0);
scala.runtime.RichInt..MODULE$.until$extension(scala.Predef..MODULE$.intWrapper(0), xEnd).foreach((x) -> {
scala.runtime.RichInt..MODULE$.until$extension(scala.Predef..MODULE$.intWrapper(0), yEnd).foreach((y) -> {
int var3 = sum.elem + 1;
sum.elem = var3;
});
});
return sum.elem;
}
And javap output:
Scala2:
private int sumWithRange(int, int);
Code:
0: iconst_0
1: invokestatic #187 // Method scala/runtime/IntRef.create:(I)Lscala/runtime/IntRef;
4: astore_3
5: getstatic #192 // Field scala/runtime/RichInt$.MODULE$:Lscala/runtime/RichInt$;
8: getstatic #62 // Field scala/Predef$.MODULE$:Lscala/Predef$;
11: iconst_0
12: invokevirtual #196 // Method scala/Predef$.intWrapper:(I)I
15: iload_1
16: invokevirtual #200 // Method scala/runtime/RichInt$.until$extension:(II)Lscala/collection/immutable/Range;
19: iload_2
20: aload_3
21: invokedynamic #210, 0 // InvokeDynamic #2:apply$mcVI$sp:(ILscala/runtime/IntRef;)Lscala/runtime/java8/JFunction1$mcVI$sp;
26: invokevirtual #214 // Method scala/collection/immutable/Range.foreach$mVc$sp:(Lscala/Function1;)V
29: aload_3
30: getfield #218 // Field scala/runtime/IntRef.elem:I
33: ireturn
Scala3:
private int sumWithRange(int, int);
Code:
0: iconst_0
1: invokestatic #185 // Method scala/runtime/IntRef.create:(I)Lscala/runtime/IntRef;
4: astore_3
5: getstatic #190 // Field scala/runtime/RichInt$.MODULE$:Lscala/runtime/RichInt$;
8: getstatic #144 // Field scala/Predef$.MODULE$:Lscala/Predef$;
11: iconst_0
12: invokevirtual #194 // Method scala/Predef$.intWrapper:(I)I
15: iload_1
16: invokevirtual #198 // Method scala/runtime/RichInt$.until$extension:(II)Lscala/collection/immutable/Range;
19: aload_0
20: iload_2
21: aload_3
22: invokedynamic #209, 0 // InvokeDynamic #2:apply$mcVI$sp:(Lscalashop/TestDoubleLoopPerformance$;ILscala/runtime/IntRef;)Lscala/runtime/java8/JFunction1$mcVI$sp;
27: invokevirtual #213 // Method scala/collection/immutable/Range.foreach:(Lscala/Function1;)V
30: aload_3
31: getfield #217 // Field scala/runtime/IntRef.elem:I
34: ireturn
Conclusions
Scala2 uses Range.foreach$mVc$sp
Scala3 uses Range.foreach
Original SO post:
https://stackoverflow.com/questions/68941194/range-benchmark-sluggish-after-migrating-to-scala3
Expectation
Performance of Scala3 code is on par with Scala2