Performance question related to boxing and interface implementation

I have a library called Kds, where I have specialized versions for ArrayList for Double, Int and Float.
Recently someone reported lots of allocations and I was aware that something I thought was optimized was not true.

Check this PR:

I have something like this:

class DoubleArrayList(capacity: Int = 7) : List<Double> {
    var data: DoubleArray...
    override operator fun get(index: Int): Double = data[index]
}

I thought that internally it would create a get method without doing boxing, and then the overriden method calling that method and doing the boxing, so when calling the getter directly for that class, the method not doing boxing would be called. It seems to not be the case.
So I have ended creating a separate method called getAt. But that prevents me to using the nice getter syntax, while still implementing the interface.

Is there a way for doing this while implementing the interface, if not, there is an issue already reported for this? Is the new backend having this into account?

Thanks in advance

List.get() takes an object reference as its parameter. So if you want to implement/override it, your method needs to take an object reference as its parameter. Value types couldn’t be passed directly, but would have to be boxed.

You’re free to add a method taking a value type, but that would have to be an overload – a separate method with a different signature – and couldn’t implement the interface.

(That’s why Kotlin’s primitive-valued arrays aren’t subclasses of the general Array type.)

This is not a bug. Instances of type parameters are always boxed objects, because the JVM (for both Java and Kotlin) uses the same code for the method implementation no matter what the type argument is – List<Double> is used in exactly the same way as List<AnythingElse> In the code, all type arguments are effectively replaced with Any/Object (except reified types in Kotlin inline functions). This is called “type erasure”, and it annoyingly prevents Java and Kotlin from providing optimized implementations of the generic collections for primitive types.

The bottom line is that you do not want to implement List<Double> or any other generic collection interface. You can make a DoubleList interface if you like, and extend that. Of course you’d have to make IntList, etc., for all the primitive types you want to support.

Gnu Trove is a nice Java implementation of this sort of thing that you might want to look at: http://trove4j.sourceforge.net/html/overview.html I think all of their primitive collection types are auto-generated by translating a single template source into different Java source files.

I think what @soywiz expected is that kotlin would generate 2 methods. Something like

class DoubleArrayList : List<Double> {
    operator fun getNoBoxing(i: Index): Double = ...
    override operator fun get(i: Index): Object = Double(getNoBoxing())
}

That way kotlin could use the non boxing version when you call get on DoubleArrayList and only use the boxing version if the function is called “through” the interface.

As wasabi said thats the case. In kotlin you are not differentiating primitive from ibject types like in java. double vs java.lang.Double. In kotlin it is just Double.

From the JVM perspective you can create methods with the name name and different signature.

Right now it is creating:

get()Ljava/lang/Object;
get()Ljava/lang/Double;

What if additionally (or replacing the Double variant) it also creates:
get()D

Yeah, I can avoid implementing the List interface (breaking binary compatibility by the way and preventing using some extension method) or create a separate method like I did.

But kotlin tries to fix many Java flaws and I think this could be fixed too in a future version. I wanted to know if this is reported, interesting, or considered.

The compiler does actually does generate overloads that use primitive types, but only when the primitive is present as a parameter, not as a return type. For instance in this case, the generated class contains the following two overloads of add:

   public boolean add(double var1) {
      throw new UnsupportedOperationException("Operation is not supported for read-only collection");
   }

   // $FF: synthetic method
   public boolean add(Object var1) {
      throw new UnsupportedOperationException("Operation is not supported for read-only collection");
   }

set is even more fun, as it compiles to a method that accepts a double and returns Double.