I have recently been told that it is sometime worth to replace Scala Option code with an old-style if-null check for the performance sake. So I decided to test it. It is very hard to come up with a test that would expose such a small difference, but I think I manage to do that.
I used JMH framework from OpenJDK/Oracle and here is the meat of the test class:
@Benchmark
def testOption(): Any = {
val value: java.lang.Long = 4L
Option(value).map(_ * 2).getOrElse(1L)
}
@Benchmark
def testNull(): Any = {
val value: java.lang.Long = 4L
if(value != null) value * 2 else 1L
}
@Benchmark
def testPlus(): Any = {
val value: java.lang.Long = 4L
if(value != null) (value + 1) * 2 else 1L
}
And the result is an interesting one:
Benchmark Mode Samples Score Error Units
o.s.MyBenchmark.testNull avgt 100 3.227 ± 0.016 ns/op
o.s.MyBenchmark.testOption avgt 100 3.286 ± 0.022 ns/op
o.s.MyBenchmark.testPlus avgt 100 3.325 ± 0.023 ns/op
You can see that using Option incurs on average extra 0.05 nanosecond in execution time. Yes, you read it right, it is 1/20th of a nanosecond. It is small. It is so insanely small, that an extra delay caused by a single ADD is twice as big as this one (0.1 nanosecond). Do not freak out on the measurement method - I have not tried to time the execution. The test was executed for a second and counted the number of executions. The result you see is a product of dividing one by the other, plus all the precautions were taken to reasonably warm up JVM and execute the test many times. The full test took 15 minutes to complete. You can read more about how JMH works, it is truly an amazing piece of technology.
But back to our question. In the trivial case performance penalty for using Scala Option is negligible. But by using it you end up with a much better code that will be easier to support later on.
My guess is that in this case we see the Java's glorious Escape Analysis at its best. The canonical example of it is:
public long timeSince(long epochTime) {
return (new Date()).getTime() - epochTime;
}
public long timeSince2(long epochTime) {
return System.currentTimeMillis() - epochTime;
}
Surprisingly, the two methods will produce exactly the same machine code. But what about penalty of allocating java.util.Date instance on heap you may ask? There is none. JVM's JIT compiler (unless you disable escape analysis or use an ancient one) detect that you never really use the full instance (it never leaves the method) and you only ever access its fastTime field, which is also the only field the constructor initializes. The compiler also notices that this field is of type long, that can be easily allocated on stack. Armed with that information it never touches the heap in the code it emits. Brilliant, is not it?
In conclusion, if you see a possibility of null appearing in your Scala code (probably because of interop with some Java library), do not hesitate to wrap it in Scala Option right away.