Avoid allocations in Executable.getAllGenericParameterTypes

Christoph Dreis christoph.dreis at freenet.de
Sat May 23 07:53:59 UTC 2020


Hi,

I was looking through the code and found a relatively straight-forward optimization in Executable.getAllGenericParameterTypes (attached below).
The idea is to simply move some code around to avoid unnecessary allocations from getParameters() or array initializations when working with generics and no "real" parameter data is available.

I used the following benchmark:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {

    @State(Scope.Benchmark)
    public static class ThreadState {
        
        private Parameter parameter;

        public ThreadState() {
            try {
                this.parameter = Test1.class.getDeclaredConstructor(Set.class).getParameters()[0];
            } catch (NoSuchMethodException e) {
                // Do nothing
            }
        }

    }

    @Benchmark
    public AnnotatedType test(ThreadState threadState) {
        return threadState.parameter.getAnnotatedType();
    }

}

public class Test1 {
      public Test1(Set<?> names) {}
}

With the attached patch and the benchmark above I get the following results.

Old
Benchmark                                                  Mode  Cnt    Score     Error   Units
MyBenchmark.test                                       avgt   10  358,103 ±  58,098   ns/op
MyBenchmark.test:·gc.alloc.rate.norm    avgt   10  248,021 ±   0,003    B/op

Patched
MyBenchmark.test                                       avgt   10  309,133 ±  6,319   ns/op
MyBenchmark.test:·gc.alloc.rate.norm    avgt   10  200,016 ±  0,001    B/op


I don't think the particular method is called super often, but it seems to be used in ByteBuddy & JUnit as far as I can see.
In case you think this is worthwhile, I would appreciate it if someone can sponsor this patch.

Cheers,
Christoph

========= PATCH ==========
--- a/src/java.base/share/classes/java/lang/reflect/Executable.java     Wed May 13 16:18:16 2020 +0200
+++ b/src/java.base/share/classes/java/lang/reflect/Executable.java     Sat May 23 08:31:23 2020 +0200
@@ -307,12 +307,12 @@
             final boolean realParamData = hasRealParameterData();
             final Type[] genericParamTypes = getGenericParameterTypes();
             final Type[] nonGenericParamTypes = getParameterTypes();
-            final Type[] out = new Type[nonGenericParamTypes.length];
-            final Parameter[] params = getParameters();
-            int fromidx = 0;
             // If we have real parameter data, then we use the
             // synthetic and mandate flags to our advantage.
             if (realParamData) {
+                final Type[] out = new Type[nonGenericParamTypes.length];
+                final Parameter[] params = getParameters();
+                int fromidx = 0;
                 for (int i = 0; i < out.length; i++) {
                     final Parameter param = params[i];
                     if (param.isSynthetic() || param.isImplicit()) {
@@ -325,6 +325,7 @@
                         fromidx++;
                     }
                 }
+                return out;
             } else {
                 // Otherwise, use the non-generic parameter data.
                 // Without method parameter reflection data, we have
@@ -334,7 +335,6 @@
                 return genericParamTypes.length == nonGenericParamTypes.length ?
                     genericParamTypes : nonGenericParamTypes;
             }
-            return out;
         }




More information about the core-libs-dev mailing list