Custom arrays help JIT optimize better?

My question is this:

Its a common collection of collections common for Java code:

public class GenericCollection<T> { private Object[] data; public GenericCollection () { // Backing array is a plain object array. this.data = new Object[10]; } @SuppressWarnings( "unchecked" ) public T get(int index) { // And we just cast to appropriate type when needed. return (T) this.data[index]; } } 

And used like this, for example:

 for (MyObject obj : genericCollection) { obj.myObjectMethod(); } 

Since the genericCollection general type is erased, the JVM does not seem to know that there really are only MyObject instances inside the "genericCollection" data array, since the actual array type is Object, there may be a String in it, and calling the myObjectMethod method on it will cause exceptions.

So, I assume that the JVM needs to do some runtime gymnastics testing to find out what really is inside the GenericCollection instance.

Now consider this implementation:

 public class GenericCollection<T> { private T[] data; @SuppressWarnings( "unchecked" ) public GenericCollection ( Class<T> type ) { // Create a type specific array. this.data = (T[]) Array.newInstance( type, 10 ); } public T get ( int index ) { // No unsafe casts needed. return this.data[index]; } } 

In this case, we create a specific array type through reflection, so the JVM could suggest that in this context there can only be T objects inside this array, which makes unnecessary throws and possible costly type checks redundant.

My question would be, given what HotSpot can do, will it help in any way, in terms of performance, to implement shared collections with the “appropriate” type of backup array?

For example, does HotSpot help remove unnecessary type checks or drops? Perhaps, perhaps this will make it easier to inline methods, given that the support array is of a specific type?

+7
java optimization generics jvm jvm-hotspot
source share
2 answers

Not in this particular case.

The general array T[] is erased to Object[] in bytecode. The array aggregator for Object[] always returns Object , so it does not need to check the actual type of the array. Therefore, there is no use in using T[] instead of Object[] for the operation of getting the array. In both cases, there is an aaload instruction followed by a checkcast , and it works the same way.

At the same time, if the array parameter is worse for a typed array, rather than Object[] , because aastore should check if the value matches the actual type of the array component.

That is, the proposed modification works equally for get , but performs worse for set . This can be confirmed using the following JMH test.

 package bench; import org.openjdk.jmh.annotations.*; import java.lang.reflect.Array; @State(Scope.Benchmark) public class Generics { private ObjectArray<String> objectArray; private GenericArray<String> genericArray; private StringArray stringArray; private int index; @Param("100000") private int length; @Setup public void setup() { genericArray = new GenericArray<>(String.class, length); objectArray = new ObjectArray<>(length); stringArray = new StringArray(length); for (int i = 0; i < length; i++) { String s = Integer.toString(i); objectArray.set(i, s); genericArray.set(i, s); stringArray.set(i, s); } } @Benchmark public String getGenericArray() { return genericArray.get(nextIndex()); } @Benchmark public String getObjectArray() { return objectArray.get(nextIndex()); } @Benchmark public String getStringArray() { return stringArray.get(nextIndex()); } @Benchmark public void setGenericArray() { genericArray.set(nextIndex(), "value"); } @Benchmark public void setObjectArray() { objectArray.set(nextIndex(), "value"); } @Benchmark public void setStringArray() { stringArray.set(nextIndex(), "value"); } private int nextIndex() { if (++index == length) index = 0; return index; } static class GenericArray<T> { private T[] data; @SuppressWarnings("unchecked") public GenericArray(Class<T> type, int length) { this.data = (T[]) Array.newInstance(type, length); } public T get(int index) { return data[index]; } public void set(int index, T value) { data[index] = value; } } static class ObjectArray<T> { private Object[] data; public ObjectArray(int length) { this.data = new Object[length]; } @SuppressWarnings("unchecked") public T get(int index) { return (T) data[index]; } public void set(int index, T value) { data[index] = value; } } static class StringArray { private String[] data; public StringArray(int length) { this.data = new String[length]; } public String get(int index) { return data[index]; } public void set(int index, String value) { data[index] = value; } } } 

And the results:

 Benchmark (length) Mode Cnt Score Error Units Generics.getGenericArray 100000 avgt 40 5,212 ± 0,038 ns/op <- equal Generics.getObjectArray 100000 avgt 40 5,224 ± 0,043 ns/op <- Generics.getStringArray 100000 avgt 40 4,557 ± 0,051 ns/op Generics.setGenericArray 100000 avgt 40 3,299 ± 0,032 ns/op <- worse Generics.setObjectArray 100000 avgt 40 2,456 ± 0,007 ns/op <- Generics.setStringArray 100000 avgt 40 2,138 ± 0,008 ns/op 
+6
source share

Not. type of erasure The Java manual explains

Generalizations were introduced into the Java language to provide more stringent type checks at compile time and to support general programming. To implement generics, the Java compiler applies style erasure to:

  • Replace all type parameters in generic types with your borders or object if the type parameters are not limited. Thus, the obtained bytecode contains only ordinary classes, interfaces, and methods.
  • Insert the type if necessary to maintain type safety.
  • Creating bridge methods for preserving polymorphism in extended generic types.

Thus, after compilation, the general types of Object .

+2
source share

All Articles