Java 17 came with a preview of JEP 406. This makes it possible to pattern match based on the type of the scrutinee expression. Looking at the javap -c output of these new type-based switches, I see that they work using a new indy typeSwitch bootstrap method followed by a tableswitch. I suspect this is more efficient than the nested if strategy used by the Scala compiler.
Given that type-based match is quite a bit more widespread in Scala (especially on sealed types), is there any interest/proposal for adopting the (presumably more efficient) compilation strategy that Java uses? Obviously, we’d need to create our own bootstrap method, but I don’t see any reason this wouldn’t be possible…
It could be the case that the bootstrap method actually is generating a while loop underneath that checks the cases
e.g.
static String formatterPatternSwitch(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}
gets translated to the equivalent of
def lookup(obj: Any, cases: Array[Class[_]]): Int = {
val dynCls = obj.getClass
var i = 0
while (i < cases.length) {
if (cases(i).isAssignableFrom(dynCls))
return i
i += 1
}
return cases.length
}
lookup(foo, Array(classOf[jl.Integer], classOf[jl.Long], classOf[jl.Double], classOf[jl.String])) match {
case 0 => String.format("int %d", foo.asInstanceOf[jl.Integer])
case 1 => String.format("long %d", foo.asInstanceOf[jl.Long])
case 2 => String.format("double %f", foo.asInstanceOf[jl.Double])
case 3 => String.format("String %s", foo.asInstanceOf[jl.String])
case _ => foo.toString
}
but maybe that could be made an intrinsic of the vm
I think the current implementation does effectively translate into a while loop, but that’s a hopefully temporary implementation detail. This seems quite similar to the whole indy-fing of string concatenation: by moving the code for handling a high-level construct (match in this case) to the runtime you get
ability to improve the bootstrap method in future Scala releases without breaking binary compatibility
not sure this one actually improves performance, because it delegates to a while loop over an array of classes to produce the int that gets switched on
the idea of bootstrap methods it to do heavy lifting on linking time (which is late in java) and then the results are cached and subsequent invocations are cheap.
look at e.g. JEP 280: Indify String Concatenation which replaces inline string concatenation with invocation of bootstrap methods. that makes whole thing heavier on first invocation, but also avoids embedding elaborate bytecode directly in application code.
when compilers that output bytecode are producing bytecode for string concatenation, the jvm needs to recognize these bytecode forms and then optimize them knowing the whole thing is about string concatenation, i.e. jvm has special optimizations for string concatenation, but first it must recognize known bytecode sequences that are compatible with such special optimizations. that makes the whole performance optimization quite fragile. opting for invokedynamic / boostrap methods instead allows for future optimizations without worrying if elaborate bytecode sequences in application code get correctly recognized.
similar thing can happen to pattern matching. even if the performance is not improved today compared to inline elaborate bytecode sequences, the performance could be optimized in future jvm versions without changing the application bytecode and without worrying if inline expanded bytecode gets correctly recognized, so special optimizations can apply.
in the end, it’s worth keeping an eye on how java handles pattern matching on class hierarchies in the future. java authors could develop some special optimizations in bootstrap methods for pattern matching, so that would be then beneficial to use also for compiling scala code. for now, let’s use what’s the fastest solution now.