Sequences of unboxed primitives in JavaFX

Read JavaFX-sequence-basics first.

A sequence whose JavaFX type is T[] translates to the Java generic type Sequence<? extends T>. A primitive type like Integer is mapped to the Java primitive type int. Unfortunately, the JVM doesn't support generics with primitive type parameters such as Sequence<int>; instead we have to use Sequence<java.lang.Integer>. An ArraySequence would be implemented using an array of java.lang.Integer instances, which are much more expensive than a Java int array: java.lang.Integer requires memory allocation, which takes more time and memory. Before you can do arithmetic the java.lang.Integer has to be converted to an int. Converting an int to a java.lang.Integer (typically using Integer.valueOf(int)) is called boxing, because you create a box (the Integer object) to hold the value. Converting a java.lang.Integer to an int (typically using Integer#intValue()) is called unboxing.

As boxing is quite expensive, we'd like to find a way to use sequences of primitive unboxed values directly.

Abandoned approach: IntegerSequence

One approach is to define a new interface for each primitive type:

public interface IntegerSequence extends Sequence<java.lang.Integer> {
    int getAsInt(int position);
    void toArray(int sourceOffset, int length, int[] array, int destOffset);
}

Then the compiler would translate Node[] to Sequence<Node>, while Integer[] would be translated to IntegerSequence.

We'd create similar XxxSequence interfaces for each unboxed type Xxx. Likewise for XxxAbstractSequence, XxxArraySquence, and so on. Generating these classes can be done conveniently with a template processor.

Because a Sequence<Integer> doesn't implement IntegerSequence, every sequence class that depends on a type parameter T would need to be replicated for each primitive type. While there aren't very many Sequence interfaces and classes, it's still somewhat costly in terms of static footprint.

Naively, the sequence-variable classes also have to be multiplied, but it is possible to be smarter. For example, we could parameterize the SequenceVariable class in terms of both the element type and the sequence type:

class SequenceVariable<T, TSEQ extends Sequence<T>> {
    /** Get current value, as a Sequence. */
    public TSEQ get() { ... }
    ...
}

An Integer[] variable would be compiled to a Sequencevariable<Integer,IntegerSequence>. However, this was getting complicated, plus it entailed a lot of code-duplication so I set this approach aside.

Direct unboxed support in Sequence

Instead, I added support for the primitive types directly in Sequence:

public interface Sequence<T> {
    public T get(int position);
    ... other methods as before ...;

    byte getAsByte(int position);
    short getAsShort(int position);
    int getAsInt(int position);
    ... etc ...;    
}

Similarly for AbstractSequence, which provides default implementations in terms of get:

public class AbstractSequence<T> implements Sequence<T> {
     public int getAsInt(int position) {
        return ((Number) get(position)).intValue();
    }
    ... etc ..;
}

We do need separate ArraySequence sub-classes for each primitive type. (The following is simplified in that it ignores the buffer-gap.)

class abstract ArraySequence<T> extends AbstractSequence<T> {
    protected abstract T getRawArrayElementAsObject(int index);

    public T get(int position) 
         if (position < 0 || position >= array.length)
             return getDefaultValue();
         return getRawArrayElementAsObject(position);
    }
}
class ObjectArraySequence<T> extends ArraySequence<T> {
    T[] array;

    protected abstract T getRawArrayElementAsObject(int index) {
        return array[index];
    }
}
class IntegerArraySequence extends ArraySequence<java.lang.Integer>
    int[] array;

   protected Integer getRawArrayElementAsObject(int index) {
       return Integer.valueOf(array[index]);
   }

   public int getAsInt(int position) {
        if (position < 0 || position >= array.length)
            return 0;
        else
            return array[position];
    }
}

This works pretty well. The compiler translates:

def x : Integer[] = ...;
x[i]

to

SequenceVariable<java.lang.Integer> x = ....;
x.get().getAsInt(i)

Then if the value in x is an IntegerArraySequence then we end up calling IntegerArraySequence#getAsInt, which does no boxing or unboxing. Note it is possible the sequence value at run-time is an ObjectArraySequence<Integer> rather than an IntegerArraySequence. That could happen in the case of bind forms, or other expressions which have not (yet) been optimized to avoid boxing. The right thing still happens in those cases, though with the boxing overhead.

Various methods in SequenceVariable also needed to be replicated for each primitive type.

Tags: