6

I love the power of Scala's for comprehensions and the way they integrate with any monadic type with map and flatMap. However, I'd also like to do simple integer loops without a major speed penalty. Why doesn't Scala have the following two logically identical loops run with similar runtime performance or even compile into similar byte code?

// This is slow...
for (i <- 0 until n) println(s"for loop with $i")

// This runs much faster. It runs roughly at the same speed as Java code doing an identical while or for loop.
var i = 0;
while (i < n) {
  println(s"while loop with $i")
  i += 1
}
user2684301
  • 2,550
  • 1
  • 24
  • 33

1 Answers1

6

The main (but not only) reason why they're different is boxing.

In the code:

for (i <- 0 until n) println(s"for loop with $i")

You're passing an anonymous function println(s"for loop with $i") into the comprehension for (i <- 0 until n). It's equivalent to:

(0 until n) foreach (i =>
  println(s"for loop with $i")
}

That function is erased in the bytecode, which means that i can't be a primitive int, it has to be boxed as an Integer instead. Java doesn't have Fixnum references to avoid this cost in the same way that e.g. Smalltalk does (especially frustrating, given how much older smalltalk is!)

Using -optimize can help in some cases, especially in trunk builds of scalac.

You can also use scalaxy/loops to speed things up :)

Kevin Wright
  • 49,540
  • 9
  • 105
  • 155
  • Be careful with the -optimize option as some libraries have trouble with it. I believe Akka is one of them. – Giovanni Botta Jan 27 '14 at 15:39
  • This is informative, but it's still just an excuse. The Scala compiler can clearly fix this. Boxing ints into Integers may be easier and cleaner for the compiler implementation, but there is no reason the byte code can't use unboxed Java primitive ints. – user2684301 Jan 27 '14 at 16:20
  • 2
    @user2684301 That would be a cross-project optimisation, which the current stable compiler doesn't do. `Range.foreach` takes a *Generic* function, so optimising would need the implementation of `foreach` to be copied from the standard library into the using code and then specialised - which causes all kinds of fun for binary compatibility. – Kevin Wright Jan 27 '14 at 20:17