Is it possible to optimize Array.plus operator considering empty arrays?

Let’s say we have some code that works with arrays (not lists, because of some platform APIs or third-party library):

fun compute() : Array<String> {
    val firstArray = computeFirst()
    val secondArray = computeSecond()
    return firstArray + secondArray
}

In my cases both computeFirst and computeSecond may return empty array as a result.

Plus operator for arrays is defined in _ArraysJVM.kt of stdlib as:

public actual operator fun <T> Array<T>.plus(elements: Array<out T>): Array<T> {
    val thisSize = size
    val arraySize = elements.size
    val result = java.util.Arrays.copyOf(this, thisSize + arraySize)
    System.arraycopy(elements, 0, result, thisSize, arraySize)
    return result
}

On JVM an empty array is effectively immutable and can be reused in case of two empty arrays are concatenated with .plus(). How is it implemented in Kotlin JS and Native worlds?

Is it possible to optimize Array.plus operator considering empty arrays? E.g. if both operands are empty, return one of them as a result? It seems it is possible to optimize this at least for JVM in _ArraysJVM.kt.

Example:

public actual operator fun <T> Array<T>.plus(elements: Array<out T>): Array<T> {
    if (this.isEmpty() && elements.isEmpty()) return this

    val thisSize = size
    val arraySize = elements.size
    val result = java.util.Arrays.copyOf(this, thisSize + arraySize)
    System.arraycopy(elements, 0, result, thisSize, arraySize)
    return result
}
1 Like

Sure you can reuse either empty array if both are empty but you can’t do the same if only one is empty, because you still need to return a new array that won’t modify the original.
My guess is that java.util.Arrays.copyOf and System.arraycopy are so optimized that any kotlin optimization might just slow things down at least on the JVM. I would do some serious benchmarks before changing anything here.
Not sure about JS or native implementation but I don’t think copying 0 elements has a big performance overhead. If it is slower, it’s probably not much worse than the required if-statment. No idea how JS behaves here, but I doubt it will lead to any noticeable effect as long as JS is as slow as it is atm. If I remember recent comparisions posted here JS is about 100x slower than JVM (native is only 20x slower) so there are other places that could need some performance improvements first.

2 Likes

That would be a breaking change. It would change the behavior of this program:

fun main() {
    val arr1: Array<String> = arrayOf()
    val arr2: Array<String> = arrayOf()
    
    println(arr1 + arr2 == arr1)
    println(arr1 + arr2 == arr2)
}
2 Likes

This is definitely a change in behavior, but whether it’s breaking or not depends on the plus function contract. All it promises is that the returned array contains all elements of the argument arrays, but doesn’t say anything about its identity.

4 Likes

That may have been my post from a few days ago. JS was 100 times slower than native, with native another 20 slower than JVM. So yes, 2000 times slower.

Ofccourse this in the specific case of a programing language interpreter. Other applications may have different performance characteristics so don’t take my numbers as definite. I do think it’sa rreasonable average.

1 Like

Sure, only case with zero length arrays can be optimized since non-empty array is mutable and should not be reused as the result.

The main reason for optimization for me is not performance but memory allocation of new arrays - instances count and new memory allocated for instances.

1 Like

I highly doubt the plus operator is applied to empty arrays often enough that the additional memory allocation is going to make any difference in any real application. In fact, I would not be surprised if the additional if statement is going to cost more time than what is saved.