https://kotlinlang.org logo
#getting-started
Title
# getting-started
a

Ayfri

02/08/2022, 3:50 PM
Hi, I have a simple question, why inline function is only useful when there is a lambda as last argument ? Why couldn't use inline functions at other places ?
r

Ruckus

02/08/2022, 3:59 PM
It doesn't have to be the last argument. Inline will also inline any lambdas passed in, which means they aren't allocating
Function
objects for them (and often can avoid boxing of primitive generics). That can have a notable performance/overhead impact. Without a lambda to inline, there isn't much you can expect in terms of gains, so there isn't much reason to do it.
(This is ignoring reified generics of course, where inline is not only useful but required.)
That being said, there is a small but non-zero impact to function calls, so some types of functions called in hot code can benefit from inlining despite the warning (e.g. common math operations).
Also, I could be wrong, but I don't believe Kotlin will inline lambda receivers, only "normal" arguments (someone please correct me if I'm off base with that one).
c

Casey Brooks

02/08/2022, 4:19 PM
The main benefit for most Kotlin code with inlining is to remove the overhead of function calls. Typical Kotlin code would see this gain primarily from lambdas, which don't always "look" like function calls but can still accrue a high overhead cost if you're not careful. Inlining allows one to repeatedly call a "block of code" without incurring the runtime cost of function calls. The warnings that IntelliJ gives you when inlining without a lambda is because the impact to binary size and runtime optimization is usually worse than not inlining. The JVM is really good at detecting and optimizing hot code paths and inlining that bytecode at runtime, giving similar performance boosts to compile-time inlining. But overzealous compile-time inlining can actually be a detriment to runtime performance, because it hinders the JVM's ability to do runtime optimization. So to be safe, IntelliJ gives a warning to help you use inline where it makes the most sense, without blowing up your binary size and hindering runtime optimization from inlining too much stuff. All that said, there is still a use-case for inlining large chunks of code that primarily deal with raw computation. As inlining happens relatively early in the compilation pipeline, it may enable compiler optimizations later on that avoid boing/unboxing primitives, or collapse a bunch of constant values or operators into simpler expressions collected from all over the place in the code. Looking through some popular Kotlin libraries that deal heavily with math or graphics, you'll find that a lot of operators and functions that work with primitive values are inlined without lambdas, for this very reason.
👍 2
e

ephemient

02/08/2022, 4:24 PM
the stdlib itself has
Copy code
inline fun <T, K, V> Iterable<T>.associateBy(
    keySelector: (T) -> K,
    valueTransform: (T) -> V
): Map<K, V>
where multiple lambdas, including in non-tail position, are inlined
regarding non-lambda use cases, the JVM itself will probably do a better job of inlining calls to small nonvirtual functions than you will by manually marking them as
inline
, and larger functions probably shouldn't be inlined for code size/cache friendliness reasons
a

Ayfri

02/08/2022, 4:28 PM
Okay I see, so there's still functions to inline without lambdas arguments, I see now what is the point of inlining, and I wasn't aware that JVM inlines itself some chunks of code. Also, can we say that tailrec functions are inlined ?
e

ephemient

02/08/2022, 4:30 PM
they're not exactly inlined, but rather turned into an internal loop
r

Rob Elliot

02/08/2022, 4:31 PM
I've used
inline fun
when aliasing a function using an extension function with a different name - for instance like this:
Copy code
interface Foo {
  fun badName(): String
}

inline fun Foo.goodName() = badName()
I shouldn't imagine it saves much if anything, but it "feels" right - acknowledging that this is a compiler trick, and should disappear in the bytecode just like
typealias GoodName = BadName
does. Good idea / bad idea / neither?
e

ephemient

02/08/2022, 4:33 PM
IMO marginally bad. better would be
Copy code
interface Foo {
  @Deprecated("do not use", replaceWith = ReplaceWith("goodName()"))
  fun badName(): String

  fun goodName(): String = badName()
}
to transition users to the new name as a member function
r

Rob Elliot

02/08/2022, 4:34 PM
I'm imagining not owning
Foo
e

ephemient

02/08/2022, 4:37 PM
then I still think a non-inline function would be better. if it is simple, it will still be inlined at runtime by the JVM JIT, but doesn't needlessly force the implementation to become part of your ABI
c

Casey Brooks

02/08/2022, 4:38 PM
Yeah, there are definitely lots of nuanced use-cases for inlining, but the JB folks know that the average Kotlin developer will really only encounter the lambda-overhead usecase. Unless you really know what you're doing and understand the impact to the bytecode, it's best to just follow the IntelliJ warnings and use inline where you have a lambda in a loop or need a reified generic, and don't inline otherwise
e

ephemient

02/08/2022, 4:42 PM
FYI, inline functions exist multiple times. the bytecode will contain a non-inline function (so it's callable via reflection; if it uses reified types it will throw an exception!), along with a inline-able representation of the function that gets merged into every (non-reflective) caller