Inline variadic functions


#1

Hey Kotlin community,

as far i know, inline currently only really affects functions which have higher-order-functions as parameters - But wouldn’t inlining also work for functions with a varargs-parameter?

Right now, varargs (and ... in java) are only syntactic sugar - an array is created, filled with the arguments and then passed to the function. This causes some overhead which is annoying in performance critical scenarios (e.g logging). That’s why most libraries which use variadic functions provide overloads for a small number of arguments.

afaik the compiler could inline such functions without having to create an array just for passing it as parameter - because there is no parameter to pass.

What do you think? Would such a feature have a noticeable impact on the performance of your applications? How hard would it be to implement this?


#2

I think you overlooked one problem. Inlining of varargs only works if they get accessed in a very specific way.

inline fun test(vararg elements: Foo){
   for(e in elements) ... // should be ok
   elements[someHardcodedInt] // also fine
   val index = calculateIndex
   element[index]  // how should this be accessed? Or should we just prohibit this for inlined varargs
}

The only solution to this would to create an array, which would defeat the point of the performance gain. That being said, inline adds more features to the language (like reified generics or local returns), which might profit from varargs as well.


#3

Hm, I see. But since the compiler knows the order in which the arguments are passed to a inlined variadic function, accessing the varargs by index should be possible.

The compiler could, for example, insert a check which makes sure that index is not “out of bounds” and, if it is, manually throws a ArrayIndexOutOfBoundsException.

I’m pretty sure there are more intelligent ways of solving this issue, but it doesn’t seem impossible.


#4

inline fun have some drawbacks, more and more on large functions.

So it may be possible, but should it fix the performance issues?

It is pretty hard to diagnose without good benchmarks.


#5

The bounds check is easy, accessing a random variable however is not. Consider the following:

inline fun random(vararg elements: T): T{
   val index = random(0, elements.count)
   return elements[index]
}

Either we create an array then everything is simple or we somehow need to know at compile time which element should be accessed randomly, which obviously is impossible. That leaves a last option, have a big switch everytime we randomly access an element, but this would definitely perform worse than just an array access.


#6

I don’t think so.

Firstly, the main drawback from using inline is the increased size of the compiled binary. That’s it.
Secondly, the main concern with variadic functions is the creation of an array just to pass parameters. The array is like any other object and will have to be collected by the GC. I don’t believe it’s difficult to measure the memory usage increase. As already mentioned, this is not a tiny edge case optimization. Popular libraries like SLF4J provide multiple overrides just to avoid this overhead.


#7

Good point, but i dont think that we need a “big switch”. Since all arguments lay on the stack, we could get something from the arguments at a specific index by calculating its “index” on the stack which should be easy and fast.

I’m not very familiar with the JVM Bytecode specification though. I’ll do some research , but i’m pretty sure its possible - somehow.

edit: Well - maybe it isn’t possible after all. :frowning_face:


#8

Hi @nyxcode
@Wasabi375 has explained some concerns better than me.
Log’s succesful stories does not cover many use case.

Moreover, increase the code size is a drawback and it can become a performance issue.

So, some benchmarks can be usefull, considering different array’s size.


#9

Yeah, you’re probably right.

Agree - one shouldn’t inline larger functions.

I just had the chance to talk to three guys who are familiar with the jvm and they told me that the performance gain is probably negligible since the JIT may be able to use escape analysis and not allocate anything at all. So doing a lot of work just for a few function calls until the JIT kicks in seems not to be a great idea.

Also, @Wasabi375 concerns turned out to be correct - The JVM cant dynamically load local variables.

I’ll still run some benchmarks in the next few days - inlining variadic functions with a known amount of parameters at compile time is still possible. Both of you, thanks for your input!