Run-time null checks and performance


#1

Non-nullable parameters (of functions visible to Java code) are null-checked at run time via calls to Intrinsics.checkParameterIsNotNull. For example, every time the following function is called, there will be three calls to Intrinsics.checkParameterIsNotNull:

fun foo(first: String, second: String, third: String) {}

If we call foo 100 times in a loop, Intrinsics.checkParameterIsNotNull will be called 300 times. This seems wasteful, and I wonder how this performs on Android (where I don’t know whether inlining will take place).

As stated here, it’s possible to avoid generating these calls with -Xno-param-assertions and -Xno-call-assertions, but these options are unsupported and could be removed.

Could these flags be supported? Or could Intrinsics.checkParameterIsNotNull perhaps be marked inline? I feel like this is an important topic that isn’t being taken seriously on Android.

Note that optimizing these calls away using ProGuard is not necessarily a good solution, as using ProGuard optimization isn’t strongly recommended on Android.


#2

You really try the raise awareness, don’t you :wink:


#3

I considered replying to that topic instead of creating this one, but that that topic ended up losing focus a little. I added extra information to this topic, so it’s not like I’m simply posting the same thing here.

All in all, I hope JetBrains replies, because I feel that this is an imporant issue. And Kotlin does advertise itself as “for the JVM, Android and the browser” — a lot of people believe that Kotlin supports Android as a first-class citizen.

Furthermore, any optimizations the JVM does are irrelevant and not helpful for Android.


#4

Do you have any specific evidence that those not-null checks affect performance in real Android code, or are you asking us to make these compiler parameters officially supported based on a general feeling that these checks might possibly affect performance?


#5

Sorry, I don’t have evidence. I’m not an expert at performance testing on Android, and as far as I know it’s very hard to get this right.

I just think this has enough potential to be a problem that it should be possible to switch it off. I can imagine telling any expert Android programmer “I’m going to insert a harmless function call every time you pass a reference to a non-private method” and listening to them asking about performance thereafter.

I also think this behaviour will surprise a lot of people. I wonder how many Kotlin programmers know that, for example, changing a five-parameter function from private to public adds five Intrinsics.checkParameterIsNotNull calls to the generated bytecode (if those five parameters are non-nullable references).

Thanks for your reply.


#6

As far as I know, JVM adds those null checks anyway to fulfill its specification. So in fact, adding those null assertions on the Kotlin side should speed up it a little, not slow down.

Besides, elimination of null assertions is one of the most basic and common optimisations on JVMs (Android or not), so theoretically there is nothing to worry about.

But this is all theory. To reason about the performance impact of this case one needs at least micro benchmarks. Otherwise this is all just chat. If you are going to do them, please make sure you use JMH, and later publish the code of the benchmarks.


#7

I don’t believe that null checks are relevant here, as my concern is about the calls to the method, not the null check inside that method.


#8

If only JMH ran on Android…


#9

Thinking about this again – I wonder about the idea of rewriting Intrinsics.checkParameterIsNotNull in Kotlin and adding the inline keyword. If feasible, this seems like a sensible idea.


#11

I don’t see how that Wikipedia page is relevant to this topic. ‘Intrinsics’ is just a class name that JetBrains decided to use. They could have called it almost anything. You can verify that the Kotlin compiler is generating bytecode for calling Intrinsics.checkParameterIsNotNull by using javap on the generated ‘.class’ files.


#12

Google today announced official support for Kotlin for Android development, and I saw this in their FAQ (https://developer.android.com/kotlin/faq.html):

Can I take this to mean that -Xno-param-assertions is guaranteed to be supported in future?


#13

I’m not aware of any discussions between JetBrains and Google regarding this particular option, or of any commitments from our side that would require us to keep the compatibility of advanced compiler options.


#14

In my view, it would be a good idea for this compiler flag to be officially supported, for at least the Android use case.

Although I don’t have benchmarks here, it looks like Google finds this feature important enough to mention in their Kotlin FAQ. Google could have easily left this detail out – in fact, their FAQ isn’t very long – but they clearly felt it worth addressing.


#15

Well, we have no plans to drop this feature either. To make a decision on official support, benchmarks would be essential.


#16

When working on some high-performance algorithms. I didn’t found checkNotNull to be an issue, but I had some other gotchas which cost me at times 5-10% performance according to JMH:

  • when uses Intrinsics.areEquals sometimes, when a reference comparison would suffice: https://youtrack.jetbrains.com/issue/KT-18435
  • comparing an Int with an Int? or passing it to a method with an nullable parameter invokes Integer.valueOf
  • Same thing seems to be the case when using a primitive type as generic parameter, though I didn’t find much of an performance impact in that case.
  • Equality-comparison of an Enum value if (myVal == MyEnum.ASDF) ... invokes Intrinsics.areEquals where a reference comparison would suffice.

A word of advice to the OP: If you care about performance, I would first check my code for allocations in hot codepaths, like Iterator allocations in .forEach et. al.

In hot methods I substituted forEach with the following method. (Beware that it won’t tell you about concurrent modifications!)

inline fun <T> List<T>.forEachNoAlloc(block: (T) -> Unit) {
    var idx = 0
    while (idx < size) {
        block(this[idx])
        idx++
    }
}

#17

You should file YT tockets for other things, too


#18

#19

I have evidence of it impacting performance:

image

adding -Xno-param-assertions and -Xno-call-assertions to the Kotlin compiler options solved it though