Does When...is with sealed classes encourage a performance antipattern?

I’m seeing a trend of using Sealed classes with “When … is” constructs
… a lot…

If my understanding is correct, “when … is” with sealed classes is essentially using introspection (reflection inspecting a classes type) and in the common pattern of use I am seeing, this is rendering down the type to essentially a trivial identifying property… but very, very expensive to evaluate.

Developers in my team are quite in love with this pattern, and it seems to be promoted by the Kotlin team.

I am concerned that overuse will associate poor performance with Kotlin and ultimately damage the language’s image in the industry as a whole (consider the undeserved association of C++ with over complex code; the language isn’t to blame… the way it was used is.)

I would like to understand the original vision for the usage of both Sealed classes (compared to enumeration classes) and the When…is pattern (compared to using a simple property as the selection criteria).

3 Likes

It would be nice if you checked the performance with JMH and wrote an article about it. All discussions about performance on JVM are useless without it.

1 Like

Fair point. I’m still quite interested in the original vision for sealed classes, insight into the intention can help guide correct use.

I’ve been mostly in the web world, and using is in Kotlin used to be very slow. In my applications type-safety related functions from Kotlin used to be most of the top 10 heaviest bottom-up calls. For example just retrieving an element out of a list caused an additional type check loop. However, that’s no longer the case and type safety performance has been drastically improved. I haven’t dug into exactly what’s changed, I just know that the performance is better.
Anyway, my point is the same as darksnake’s. You gotta measure it :slight_smile:
Furthermore, I encourage measurement in “real” scenarios where you’re taking a whole application and looking at where your bottlenecks are. Usually you have a few things that are actually heavy which should be optimized and then everything else combined ends up being less than 1% of total performance. Yet you’ll still hear people waste their breath saying things like, “++c is faster than c++”, “multiplication is faster than division”, etc.

Focus on the hot path.

Out of pure curiosity I looked at a couple cases for using sealed classes in a when…is check.
Tools > Kotlin > Show Kotlin Bytecode

There are definitely some interesting optimizations that can happen.

In a simple example:

sealed class Foo {

    class FooOne : Foo()
    class FooTwo : Foo()
    class FooThree : Foo()
}

fun main() {
    val f: Foo = Foo.FooOne()
    when (f) {
        is Foo.FooOne -> println("One")
        is Foo.FooTwo -> println("Two")
        is Foo.FooThree -> println("Three")
    }
}

No type checking actually happens here. Everything is stripped out except println(“One”).
There is type checking if we changed this to val f: Foo = calculateFoo()
Which does use INSTANCEOF for each condition.

I imagine that enum classes would be faster. Instead of INSTANCEOF it uses TABLESWITCH on the enumeration’s ordinal. Back to what I said earlier though, it only matters if it matters.

Enumerations being faster though to me doesn’t mean “avoid sealed classes”. I see them as different use cases entirely. Sealed classes allow polymorphism and inheritance where enums don’t, it’s comparing apples and oranges.

If for some reason when..is happens to be your performance bottleneck, your sealed class could always have an enum property then switch off of that.

2 Likes

Thanks for that, some very interesting observations.
Just to clarify, I’m not suggesting avoiding sealed classes, I don’t believe in declaring language features forbidden.
I’m more interested in understanding the appropriate use and original language design intention.
Speed performance is one aspect in some particular use cases and as observed, that really needs objective measurement.
It is great to see other peoples experiences and points of view. I really appreciate the effort taken to respond.

@darksnake I was interested in the performance of Kotlin is recently as well.
I was mostly concerned about instanceof in generated Java byte code.

I have found this test on github:

As far as I see, in the linked article author mentions about 25% to 35% slower performance comparing to use of enum-based version.

The original post is in Russian:

But just in case following is google-translated version in English:

1 Like

I am not sure. the examples for enums and instanceof are not the same and there an intermediate use of map and functionrefs. It is quite possible that difference is caused by object boxing, not by when performance. Anyway, 30% for such an operation is not a difference. I can’t imagine the case, where it would be a bottleneck.

2 Likes