Inlining Tiny Methods

Normally, when you add inline to a method that doesn’t contain any lambda parameters, you get the following compiler warning:

[NOTHING_TO_INLINE] Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types

However, within the Kotlin standard library, we can see that some methods that violate this rule are inlined, such as:

public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
public inline fun <T> listOf(): List<T> = emptyList()
public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()

These are usually methods that only make a single further method call, so there’s no expected increase in code size at the call point, and it makes sense to inline them despite the lack of lambdas. (In the case of getValue, it also allows the caller to know that the arguments are unused, which may further help performance in Kotlin 1.4.) However, why doesn’t the compiler recognize this fact and suppress the warning in these cases?

I work on an app in which the APK size is a serious concern where every KB counts, so unless the compiler is already automatically inlining these calls, being slightly more liberal with our use of inline may help, but I’m hesitant to do so while the compiler complains.

3 Likes

They are not only inline, but they also marked with @InlineOnly annotation, so those methods are not available for Java users or from reflection, so they are completely eliminated, it allows them to have fewer methods on runtime. But this is internal API of Kotlin, not available for end-user

However, why doesn’t the compiler recognize this fact and suppress the warning in these cases?

The compiler doesn’t recognize it, it’s completely manual (in case of stdlib it’s because of @InlineOnly annotation), you can do exactly the same and suppress this warning if you think this method makes sense to make inline (it makes sense without lambda only if code is small and only to reduce the amount of method calls, not bytecode size)

I work on an app in which the APK size is a serious concern where every KB counts

Inline will not help you, because inline methods still exist in bytecode.

compiler is already automatically inlining these calls,

The compiler doesn’t inline such calls automatically, it would be binary incompatible. But JVM/ART does

APK size is a serious concern

R8 will help there a lot more, it also inlines a lot of code automatically and it’s configurable

I think your question about the topic is actually a good example of why this warning is completely valid and useful, otherwise, you may use inlining for reasons which it doesn’t provide

3 Likes

Please note, that JVM JIT compiler will inline small methods on its own, so it’s not a problem to have small methods in bytecode. One should not optimize for bytecode, because your CPU executes machine code, not bytecode and JIT is where all JVM optimizations are implemented. Bytecode is left intentionally dumb.

1 Like

JVM JIT compiler will inline small methods on its own

This is exactly what I wrote in my message:

The compiler doesn’t inline such calls automatically, it would be binary incompatible. But JVM/ART does

should not optimize for bytecode, because your CPU executes machine code

It’s true mostly, but not completely, if you talk about backend it is true for most cases. But on mobile you want to have compact bytecode as possible to speed up application start and avoid multidexing (splitting bytecode into multiple DEX files to workaround) and just reduce application size, it’s even more important for libraries to reduce bytecode size (and application size, startup time)

And using tools as Proguard/R8 is crucial for it, it does not only tree shaking but also obfuscations and compile-time optimizations (including inlining), so optimizing bytecode also makes sense.

But as I said before, Kotlin inline functions (without lambda) is not a tool for it

It sounds like what I want is the functionality as @InlineOnly, but available. We use other tools to try to minimize our code size, but in my experience, they don’t always make every optimal choice, and it would be nice to be confident that a method is ultimately being inlined.

As for the JIT, while it’s effective in most cases, it’s far from a guarantee on older devices, and even then, by the time we reach the JIT it’s too late to decrease the APK size.

Inlining a method usually increases the byte code size, not decreases it, since the same byte-code is written in multiple places.

2 Likes

Especially if there are nested invocations of inclined functions… Bytecode size would grow exponentially.

1 Like

As I said in my original post, by “tiny” I’m referring to methods like Lazy<T>.getValue(), listOf(), and mutableListOf(), where the body of the method is either the same size or smaller than the code required to initially call the method.

2 Likes

This one specifically is inlined for a specific optimization in Kotlin 1.4 that basically makes the compiler not generate an extra array of KProperty objects if every delegate has an inlined .getValue method and doesn’t use the property parameter.

2 Likes