Hidden allocations when using vararg and spread operator


#1

As I understand it, if I pass arguments to a vararg function like so:

function("a", "b", "c", "d")

a new Array will be created to hold them. But what about this:

val someStringArray<String> = ...
function(*someStringArray)

Is this “safe” in term of additional memory allocations and no temporary Array will be created or it works exactly the same as the first example and a new array is needed?


#2

Spread operator always copies an array, so that neither the function nor its caller are required to make defensive copies of the array.
It might be possible for the compiler to optimize such copying away, for example when one vararg function passes its array to another vararg function and doesn’t use it itself after, however such optimization has not been baked yet.


#3

You can check with Tools > Kotlin > Show Kotlin Bytecode

Here’s my sample code:

fun doStuff(vararg data : String) {

}

fun main(args: Array<String>) {
    val arr = arrayOf("a", "b", "c")
    doStuff(*arr)
}

Here is what is created for the call to doStuff

    LINENUMBER 9 L6
    ALOAD 1
    DUP
    ARRAYLENGTH
    INVOKESTATIC java/util/Arrays.copyOf ([Ljava/lang/Object;I)[Ljava/lang/Object;
    CHECKCAST [Ljava/lang/String;
    INVOKESTATIC tfo/sample/ArraySpreadKt.doStuff ([Ljava/lang/String;)V
   L7

The Oracle Java compiler does not copy the array but passes it directly to the method:

  LINENUMBER 11 L1
    ALOAD 1
    INVOKESTATIC tfo/sample/JavaArraySpread.doStuff ([Ljava/lang/String;)V

Kotlin is using the safe approach here (copying the array) whereas Java saves the object allocation and copy.


#4

Thanks for the responses, guys. I need to start using this bytecode viewer :slight_smile:

Exactly. It looks like there’s currently no way to modify elements of array passed as vararg, so such optimization should be pretty safe.