Hi,
recently I stumbled upon unexpected behaviour regarding access to a value either via inheritance or via pattern matching.
I have a number of case classes inheriting a (zero argument) function from a trait like this
trait MixedIn {
def value: Int
}
//simplified
case class Variant1(override val value: Int) extends MixedIn
case class Variant2(override val value: Int) extends MixedIn
case class Variant3(override val value: Int) extends MixedIn
case class Variant4(override val value: Int) extends MixedIn
case class Variant5(override val value: Int) extends MixedIn
I then wrote a function for accessing the value via pattern matching.
def value(mixedIn: MixedIn): Int = {
mixedIn match {
case Variant1(value, _) => value
case Variant2(value, _) => value
case Variant3(value, _) => value
case Variant4(value, _) => value
case Variant5(value, _) => value
}
}
My expectation was that using v.value
for some v: MixedIn
should be faster than using value(v)
. I wrote micro benchmarks using 1000 uniformly distributed values:
//List of 1000 random instances of MixedIn, distributed uniformly among the above case classes.
val testData: List[MixedIn]
val valueFunction: MixedIn => Int = _.value
@Benchmark
def directly(): List[Int] = testData.map(valueFunction)
@Benchmark
def indirectly(): List[Int] = testData.map(value)
To my surprise, the directly
function takes about thrice as much time as indirectly
.
Upon tuning the overall setup, I made the following additional observations.
- There is no difference between using
def
andval
in the trait. - The number of
VariantX
has an influence on performance - using only one or two variants yields essentially the same times for both benchmarks, starting with three thedirectly
benchmark becomes slower. - The measurements are the same when I replace case classes with classes and manually written
apply
/unapply
functions. - I used
Mode.AverageTime
(time per operation) for the actual time measurements. The modeMode.Throughput
(operations per time) showed corresponding results (around one third of operations for thedirectly
benchmark in comparison with theindirectly
benchmark).
Can anyone shed some light on why this might be the case? Is there something obvious that I am missing?
Best regards
Nikita