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?
Implicit conversions in Scala are the set of methods that are apply when an object of wrong type is used. It allows the compiler to automatically convert of one type to another. Implicit conversions are applied in two conditions: First, if an expression of type A and S does not match to the expected expression type B.
An implicit conversion sequence is the sequence of conversions required to convert an argument in a function call to the type of the corresponding parameter in a function declaration. The compiler tries to determine an implicit conversion sequence for each argument.
In Scala, objects and values are treated mostly the same. An implicit object can be thought of as a value which is found in the process of looking up an implicit of its type.
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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With