Question

In Scala, is there a significant CPU or memory impact to using implicit type conversions to augment a class's functionality vs. other possible implementation choices?

For example, consider a silly String manipulation function. This implementation uses string concatenation:

object Funky {
  def main(args: Array[String]) {
    args foreach(arg => println("Funky " + arg))
  }
}

This implementation hides the concatenation behind a member method by using an implicit type conversion:

class FunkyString(str: String) {
  def funkify() = "Funky " + str
}

object ImplicitFunky {
  implicit def asFunkyString(str: String) = new FunkyString(str)

  def main(args: Array[String]) {
    args foreach(arg => println(arg.funkify()))
  }
}

Both do the same thing:

scala> Funky.main(Array("Cold Medina", "Town", "Drummer"))        
Funky Cold Medina
Funky Town
Funky Drummer

scala> ImplicitFunky.main(Array("Cold Medina", "Town", "Drummer"))
Funky Cold Medina
Funky Town
Funky Drummer

Is there any performance difference? A few specific considerations:

Does Scala inline the implicit calls to the asFunkyString method?

Does Scala actually create a new wrapper FunkyString object for each arg, or can it optimize away the extra object allocations?

Suppose FunkyString had 3 different methods (funkify1, funkify2, and funkify3), and the body of foreach called each one in succession:

println(arg.funkify1())
println(arg.funkify2())
println(arg.funkify3())

Would Scala repeat the conversion 3 times, or would it optimize away the redundant conversions and just do it once for each loop iteration?

Suppose instead that I explicitly capture the conversion in another variable, like this:

val fs = asFunkyString(arg)
println(fs.funkify1())
println(fs.funkify2())
println(fs.funkify3())

Does that change the situation?

In practical terms, is broad usage of implicit conversions a potential performance issue, or is it typically harmless?

Was it helpful?

Solution

I tried to setup a microbenchmark using the excellent Scala-Benchmark-Template.

It is very difficult to write a meaningful (non optimized away by the JIT) benchmark which tests just the implicit conversions, so I had to add a bit of overhead.

Here is the code:

class FunkyBench extends SimpleScalaBenchmark {
  val N = 10000
  def timeDirect( reps: Int ) = repeat(reps) {
    var strs = List[String]()
    var s = "a"
    for( i <- 0 until N ) {
      s += "a"
      strs ::= "Funky " + s 
    }
    strs
  }
  def timeImplicit( reps: Int ) = repeat(reps) {
    import Funky._
    var strs = List[String]()
    var s = "a"
    for( i <- 0 until N ) {
      s += "a"
      strs ::= s.funkify
    }
    strs
  }
}

And here are the results:

[info] benchmark  ms linear runtime
[info]    Direct 308 =============================
[info]  Implicit 309 ==============================

My conclusion: in any non trivial piece of code, the impact of implicit conversions (object creation) is not measurable.

EDIT: I used scala 2.9.0 and java 1.6.0_24 (in server mode)

OTHER TIPS

JVM can optimize away the extra object allocations, if it detects that would be worthy.

This is important, because if you just inline things you end up with bigger methods, which might cause performance problems with cache or even decrease the chance of JVM applying other optimizations.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top