You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polygene.apache.org by pa...@apache.org on 2016/12/03 14:52:26 UTC

[1/3] zest-java git commit: refine composite creation performance tests

Repository: zest-java
Updated Branches:
  refs/heads/develop 07f569983 -> 96080d0ac


refine composite creation performance tests

warmup runs, more iterations, pauses to let GC & JIT do their job
unsurprisingly, the results are now more accurate, realistic
measured plain object instantiation is now down to 1 nanosec
our instanciations are much slower than that


Project: http://git-wip-us.apache.org/repos/asf/zest-java/repo
Commit: http://git-wip-us.apache.org/repos/asf/zest-java/commit/e1dccf2b
Tree: http://git-wip-us.apache.org/repos/asf/zest-java/tree/e1dccf2b
Diff: http://git-wip-us.apache.org/repos/asf/zest-java/diff/e1dccf2b

Branch: refs/heads/develop
Commit: e1dccf2b6324e9541e74ffced9c3c0449fa9fbff
Parents: 07f5699
Author: Paul Merlin <pa...@apache.org>
Authored: Sat Dec 3 14:05:21 2016 +0100
Committer: Paul Merlin <pa...@apache.org>
Committed: Sat Dec 3 14:05:21 2016 +0100

----------------------------------------------------------------------
 .../CompositeCreationPerformanceTest.java       | 174 +++++++++++++------
 1 file changed, 118 insertions(+), 56 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zest-java/blob/e1dccf2b/tests/performance/src/perf/java/org/apache/zest/test/performance/runtime/composite/CompositeCreationPerformanceTest.java
----------------------------------------------------------------------
diff --git a/tests/performance/src/perf/java/org/apache/zest/test/performance/runtime/composite/CompositeCreationPerformanceTest.java b/tests/performance/src/perf/java/org/apache/zest/test/performance/runtime/composite/CompositeCreationPerformanceTest.java
index ad6c93a..e77a00f 100644
--- a/tests/performance/src/perf/java/org/apache/zest/test/performance/runtime/composite/CompositeCreationPerformanceTest.java
+++ b/tests/performance/src/perf/java/org/apache/zest/test/performance/runtime/composite/CompositeCreationPerformanceTest.java
@@ -17,21 +17,16 @@
  */
 package org.apache.zest.test.performance.runtime.composite;
 
-import java.time.Duration;
-import java.time.Instant;
-import org.apache.zest.api.time.SystemTime;
-import org.junit.Test;
 import org.apache.zest.api.activation.ActivationException;
-import org.apache.zest.api.composite.TransientBuilder;
 import org.apache.zest.api.composite.TransientBuilderFactory;
 import org.apache.zest.api.composite.TransientComposite;
 import org.apache.zest.api.object.ObjectFactory;
-import org.apache.zest.api.value.ValueBuilder;
 import org.apache.zest.api.value.ValueBuilderFactory;
 import org.apache.zest.api.value.ValueComposite;
 import org.apache.zest.bootstrap.AssemblyException;
 import org.apache.zest.bootstrap.ModuleAssembly;
 import org.apache.zest.bootstrap.SingletonAssembler;
+import org.junit.Test;
 
 /**
  * Tests performance of new composite creation.
@@ -40,7 +35,7 @@ public class CompositeCreationPerformanceTest
 {
     @Test
     public void newInstanceForRegisteredCompositePerformance()
-        throws ActivationException, AssemblyException
+        throws ActivationException, AssemblyException, InterruptedException
     {
         SingletonAssembler assembler = new SingletonAssembler()
         {
@@ -53,70 +48,122 @@ public class CompositeCreationPerformanceTest
                 module.values( AnyValue.class );
             }
         };
-        int loops = 2;
-        long t0 = 0;
+        int warmups = 10;
+        int runs = 20;
+        long waitBeforeRun = 1000;
+        long waitBetweenRuns = 500;
+        long timeForJavaObject = 0;
         {
-            for( int i = 0; i < loops; i++ )
+            // Warmup
+            for( int i = 0; i < warmups; i++ )
+            {
+                testJavaObjectCreationPerformance( false );
+            }
+            Thread.sleep( waitBeforeRun );
+            // Run
+            for( int i = 0; i < runs; i++ )
             {
-                t0 = t0 + testJavaObjectCreationPerformance();
+                timeForJavaObject += testJavaObjectCreationPerformance( true );
+                Thread.sleep( waitBetweenRuns );
             }
-            t0 = t0 / loops;
+            timeForJavaObject = timeForJavaObject / runs;
         }
-        long t1 = 0;
+        long timeForTransientComposite = 0;
         {
             TransientBuilderFactory module = assembler.module();
-            for( int i = 0; i < loops; i++ )
+            // Warmup
+            for( int i = 0; i < warmups; i++ )
             {
-                t1 = t1 + testCompositeCreationPerformance( module );
+                testCompositeCreationPerformance( module, false );
             }
-            t1 = t1 / loops;
+            Thread.sleep( waitBeforeRun );
+            // Run
+            for( int i = 0; i < runs; i++ )
+            {
+                timeForTransientComposite += testCompositeCreationPerformance( module, true );
+                Thread.sleep( waitBetweenRuns );
+            }
+            timeForTransientComposite = timeForTransientComposite / runs;
         }
-        long t2 = 0;
+        long timeForManagedObject = 0;
         {
             ObjectFactory objectFactory = assembler.module();
-            for( int i = 0; i < loops; i++ )
+            // Warmup
+            for( int i = 0; i < warmups; i++ )
+            {
+                testObjectCreationPerformance( objectFactory, false );
+            }
+            Thread.sleep( waitBeforeRun );
+            // Run
+            for( int i = 0; i < runs; i++ )
             {
-                t2 = t2 + testObjectCreationPerformance( objectFactory );
+                timeForManagedObject += testObjectCreationPerformance( objectFactory, true );
+                Thread.sleep( waitBetweenRuns );
             }
-            t2 = t2 / loops;
+            timeForManagedObject = timeForManagedObject / runs;
         }
-        long t3 = 0;
+        long timeForValueComposite = 0;
         {
             ValueBuilderFactory valueBuilderFactory = assembler.module();
-            for( int i = 0; i < loops; i++ )
+            // Warmup
+            for( int i = 0; i < warmups; i++ )
+            {
+                testValueCreationPerformance( valueBuilderFactory, false );
+            }
+            Thread.sleep( waitBeforeRun );
+            // Run
+            for( int i = 0; i < runs; i++ )
             {
-                t3 = t3 + testValueCreationPerformance( valueBuilderFactory );
+                timeForValueComposite += testValueCreationPerformance( valueBuilderFactory, true );
+                Thread.sleep( waitBetweenRuns );
             }
-            t3 = t3 / loops;
+            timeForValueComposite = timeForValueComposite / runs;
         }
 
-        long t4 = 0;
+        long timeForTransientCompositeBuilder = 0;
         {
             TransientBuilderFactory module = assembler.module();
-            for( int i = 0; i < loops; i++ )
+            // Warmup
+            for( int i = 0; i < warmups; i++ )
             {
-                t4 = t4 + testCompositeCreationWithBuilderPerformance( module );
+                testCompositeCreationWithBuilderPerformance( module, false );
             }
-            t4 = t4 / loops;
+            Thread.sleep( waitBeforeRun );
+            // Run
+            for( int i = 0; i < runs; i++ )
+            {
+                timeForTransientCompositeBuilder += testCompositeCreationWithBuilderPerformance( module, true );
+                Thread.sleep( waitBetweenRuns );
+            }
+            timeForTransientCompositeBuilder = timeForTransientCompositeBuilder / runs;
         }
-        long t6 = 0;
+        long timeForValueCompositeBuilder = 0;
         {
             ValueBuilderFactory valueBuilderFactory = assembler.module();
-            for( int i = 0; i < loops; i++ )
+            // Warmup
+            for( int i = 0; i < warmups; i++ )
+            {
+                testValueCreationWithBuilderPerformance( valueBuilderFactory, false );
+            }
+            Thread.sleep( waitBeforeRun );
+            // Run
+            for( int i = 0; i < runs; i++ )
             {
-                t6 = t6 + testValueCreationWithBuilderPerformance( valueBuilderFactory );
+                timeForValueCompositeBuilder += testValueCreationWithBuilderPerformance( valueBuilderFactory, true );
+                Thread.sleep( waitBetweenRuns );
             }
-            t6 = t6 / loops;
+            timeForValueCompositeBuilder = timeForValueCompositeBuilder / runs;
         }
 
-        System.out.println( "Transient: " + ( t1 / t0 ) + "x" );
-        System.out.println( "TransientBuilder: " + ( t4 / t0 ) + "x" );
-        System.out.println( "Value: " + ( t3 / t0 ) + "x" );
-        System.out.println( "ValueBuilder: " + ( t6 / t0 ) + "x" );
-        System.out.println( "Object: " + ( t2 / t0 ) + "x" );
+        System.out.println( "----" );
+        System.out.println( "Transient: " + ( timeForTransientComposite / timeForJavaObject ) + "x" );
+        System.out.println( "TransientBuilder: " + ( timeForTransientCompositeBuilder / timeForJavaObject ) + "x" );
+        System.out.println( "Value: " + ( timeForValueComposite / timeForJavaObject ) + "x" );
+        System.out.println( "ValueBuilder: " + ( timeForValueCompositeBuilder / timeForJavaObject ) + "x" );
+        System.out.println( "Object: " + ( timeForManagedObject / timeForJavaObject ) + "x" );
     }
 
-    private long testCompositeCreationPerformance( TransientBuilderFactory module )
+    private long testCompositeCreationPerformance( TransientBuilderFactory module, boolean run )
     {
         long start = System.currentTimeMillis();
         int iter = 1000000;
@@ -127,27 +174,32 @@ public class CompositeCreationPerformanceTest
 
         long end = System.currentTimeMillis();
         long time = 1000000L * ( end - start ) / iter;
-        System.out.println( "Minimum Composite Creation Time:" + time + " nanoseconds per composite" );
+        if( run )
+        {
+            System.out.println( "Composite Creation Time:" + time + " nanoseconds per composite" );
+        }
         return time;
     }
 
-    private long testCompositeCreationWithBuilderPerformance( TransientBuilderFactory module )
+    private long testCompositeCreationWithBuilderPerformance( TransientBuilderFactory module, boolean run )
     {
         long start = System.currentTimeMillis();
         int iter = 1000000;
         for( int i = 0; i < iter; i++ )
         {
-            TransientBuilder<AnyComposite> builder = module.newTransientBuilder( AnyComposite.class );
-            builder.newInstance();
+            module.newTransientBuilder( AnyComposite.class ).newInstance();
         }
 
         long end = System.currentTimeMillis();
         long time = 1000000L * ( end - start ) / iter;
-        System.out.println( "Minimum Composite (builder) Creation Time:" + time + " nanoseconds per composite" );
+        if( run )
+        {
+            System.out.println( "Composite (builder) Creation Time:" + time + " nanoseconds per composite" );
+        }
         return time;
     }
 
-    private long testValueCreationPerformance( ValueBuilderFactory valueBuilderFactory )
+    private long testValueCreationPerformance( ValueBuilderFactory valueBuilderFactory, boolean run )
     {
         long start = System.currentTimeMillis();
         int iter = 1000000;
@@ -158,27 +210,32 @@ public class CompositeCreationPerformanceTest
 
         long end = System.currentTimeMillis();
         long time = 1000000L * ( end - start ) / iter;
-        System.out.println( "Minimum Value Creation Time:" + time + " nanoseconds per composite" );
+        if( run )
+        {
+            System.out.println( "Value Creation Time:" + time + " nanoseconds per composite" );
+        }
         return time;
     }
 
-    private long testValueCreationWithBuilderPerformance( ValueBuilderFactory valueBuilderFactory )
+    private long testValueCreationWithBuilderPerformance( ValueBuilderFactory valueBuilderFactory, boolean run )
     {
         long start = System.currentTimeMillis();
         int iter = 1000000;
         for( int i = 0; i < iter; i++ )
         {
-            ValueBuilder<AnyValue> builder = valueBuilderFactory.newValueBuilder( AnyValue.class );
-            builder.newInstance();
+            valueBuilderFactory.newValueBuilder( AnyValue.class ).newInstance();
         }
 
         long end = System.currentTimeMillis();
         long time = 1000000L * ( end - start ) / iter;
-        System.out.println( "Minimum Value (builder) Creation Time:" + time + " nanoseconds per composite" );
+        if( run )
+        {
+            System.out.println( "Value (builder) Creation Time:" + time + " nanoseconds per composite" );
+        }
         return time;
     }
 
-    private long testObjectCreationPerformance( ObjectFactory objectFactory )
+    private long testObjectCreationPerformance( ObjectFactory objectFactory, boolean run )
     {
         long start = System.currentTimeMillis();
         int iter = 1000000;
@@ -189,11 +246,14 @@ public class CompositeCreationPerformanceTest
 
         long end = System.currentTimeMillis();
         long time = 1000000L * ( end - start ) / iter;
-        System.out.println( "Minimum Zest Object Creation Time:" + time + " nanoseconds per object" );
+        if( run )
+        {
+            System.out.println( "Zest Object Creation Time:" + time + " nanoseconds per object" );
+        }
         return time;
     }
 
-    private long testJavaObjectCreationPerformance()
+    private long testJavaObjectCreationPerformance( boolean run )
     {
         long start = System.currentTimeMillis();
         int iter = 1000000;
@@ -204,16 +264,19 @@ public class CompositeCreationPerformanceTest
 
         long end = System.currentTimeMillis();
         long time = 1000000L * ( end - start ) / iter;
-        System.out.println( "Minimum Java Object Creation Time:" + time + " nanoseconds per object" );
+        if( run )
+        {
+            System.out.println( "Java Object Creation Time:" + time + " nanoseconds per object" );
+        }
         return time;
     }
 
-    public static interface AnyComposite
+    public interface AnyComposite
         extends TransientComposite
     {
     }
 
-    public static interface AnyValue
+    public interface AnyValue
         extends ValueComposite
     {
     }
@@ -221,5 +284,4 @@ public class CompositeCreationPerformanceTest
     public static class AnyObject
     {
     }
-
 }


[2/3] zest-java git commit: Improved descriptors sorting logic to limit iterations on candidates list.

Posted by pa...@apache.org.
Improved descriptors sorting logic to limit iterations on candidates list.


Project: http://git-wip-us.apache.org/repos/asf/zest-java/repo
Commit: http://git-wip-us.apache.org/repos/asf/zest-java/commit/7ba5335c
Tree: http://git-wip-us.apache.org/repos/asf/zest-java/tree/7ba5335c
Diff: http://git-wip-us.apache.org/repos/asf/zest-java/diff/7ba5335c

Branch: refs/heads/develop
Commit: 7ba5335c3e5515821e9e6a9820843efd4a0579bb
Parents: e1dccf2
Author: Jean-Michel Tonneau <jm...@alchemytec.com>
Authored: Wed Nov 30 12:18:27 2016 +0000
Committer: Paul Merlin <pa...@apache.org>
Committed: Sat Dec 3 14:46:39 2016 +0100

----------------------------------------------------------------------
 .../zest/runtime/structure/TypeLookupImpl.java  | 102 ++++++++++++++++---
 1 file changed, 86 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zest-java/blob/7ba5335c/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookupImpl.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookupImpl.java b/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookupImpl.java
index cd077b0..5eac131 100644
--- a/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookupImpl.java
+++ b/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookupImpl.java
@@ -22,6 +22,7 @@ package org.apache.zest.runtime.structure;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Predicate;
@@ -152,33 +153,20 @@ class TypeLookupImpl
     @Override
     public List<EntityDescriptor> lookupEntityModels( final Class type )
     {
-        return entityModels.computeIfAbsent( type, key ->
-            concat(
-                allEntities().filter( ref -> new ExactTypeMatching<>( key ).test( ref ) ),
-                allEntities().filter( ref -> new AssignableFromTypeMatching<>( key ).test( ref ) )
-            ).distinct().collect( toList() )
-        );
+        return entityModels.computeIfAbsent( type, key -> new TypeMatchingDescriptors<EntityDescriptor>(key).selectedFrom(allEntities()));
     }
 
     @Override
     public ModelDescriptor lookupServiceModel( Type serviceType )
     {
         return serviceModels.computeIfAbsent( serviceType,
-                                              key -> lookupServiceModels( key ).stream().findFirst().orElse( null ) );
+                                              key -> new BestTypeMatchingDescriptors<ModelDescriptor>(key).selectedFrom(allServices()).bestMatchOrElse(null));
     }
 
     @Override
     public List<? extends ModelDescriptor> lookupServiceModels( final Type type1 )
     {
-        return servicesReferences.computeIfAbsent( type1, type ->
-        {
-            // There is a requirement that "exact match" services must be returned before "assignable match"
-            // services, hence the dual streams instead of a OR filter.
-            return Stream.concat( allServices().filter( new ExactTypeMatching<>( type ) ),
-                                  allServices().filter( new AssignableFromTypeMatching<>( type ) ) )
-                         .distinct()
-                         .collect( toList() );
-        } );
+        return servicesReferences.computeIfAbsent( type1, type ->new TypeMatchingDescriptors<ModelDescriptor>(type).selectedFrom(allServices()));
     }
 
     @Override
@@ -450,4 +438,86 @@ class TypeLookupImpl
             return value;
         }
     }
+    
+    private static class TypeMatchingDescriptors<T extends HasTypes> extends ArrayList<T> {
+
+        /**
+         * mutable :-( But performance is an issue here.
+         */
+        private Integer lastMatchingindex;
+        private final ExactTypeMatching<T> exactMatchingPredicate;
+        private final AssignableFromTypeMatching<T> assignablePredicate;
+
+        private TypeMatchingDescriptors(Type type) {
+            this.lastMatchingindex = null;
+            this.exactMatchingPredicate = new ExactTypeMatching<>(type);
+            this.assignablePredicate = new AssignableFromTypeMatching<>(type);
+        }
+
+        TypeMatchingDescriptors<T> selectedFrom(Stream<? extends T> candidates){
+            candidates.forEach(this::smartAddition);
+            return this;
+        }
+        /**
+         * Sorts the descriptors in a common list : matching ones first,
+         * assignable ones follow. The order of arrival is important :
+         * 
+         * "{assignable1, matching1, assignable2,assignable3,matching2,
+         * non-matching-or-assignable}" should result in "{ matching1,
+         * matching2, assignable1, assignable2, assignable3}"
+         */
+        private  void smartAddition(T descriptor) {
+            if (contains(descriptor)) {
+                return;
+            }
+            if ( exactMatchingPredicate.test(descriptor)) {
+                Integer nextMatchingIdx = lastMatchingindex == null ? 0 : lastMatchingindex + 1;
+                add(nextMatchingIdx, descriptor);
+                lastMatchingindex = nextMatchingIdx;
+                return;
+            }
+            if (assignablePredicate.test(descriptor)) {
+                add(descriptor);
+            }
+        }
+
+        private  boolean containsExactMatches() {
+            return lastMatchingindex != null;
+        }
+
+    }
+
+    private static class BestTypeMatchingDescriptors<T extends HasTypes> {
+
+        private TypeMatchingDescriptors<T> descriptors;
+
+        private  BestTypeMatchingDescriptors(Type type) {
+            this(new TypeMatchingDescriptors<>(type));
+        }
+
+        private  BestTypeMatchingDescriptors(TypeMatchingDescriptors<T> descriptors) {
+            this.descriptors = descriptors;
+        }
+
+        BestTypeMatchingDescriptors<T> selectedFrom(Stream<? extends T> candidates) {
+            candidates.forEach(this::smartAddition);
+            return this;
+        }
+        
+        private T bestMatchOrElse(T or) {
+            return !descriptors.isEmpty() ? descriptors.get(0) : or;
+        }
+
+        /**
+         * We want the first matching if exists, the first assignable otherwise.
+         * While there is no matching descriptor, even if we found assignable
+         * ones, we keep searching in case the last element is a matching type.
+         */
+        private void smartAddition(T descriptor) {
+            if (!descriptors.containsExactMatches()) {
+                descriptors.smartAddition(descriptor);
+            }
+        }
+    }
+
 }


[3/3] zest-java git commit: typelookup: refine Jean-Michel’s contribution

Posted by pa...@apache.org.
typelookup: refine Jean-Michel\u2019s contribution

formatting, naming, documentation
no behavior change

Closes #2


Project: http://git-wip-us.apache.org/repos/asf/zest-java/repo
Commit: http://git-wip-us.apache.org/repos/asf/zest-java/commit/96080d0a
Tree: http://git-wip-us.apache.org/repos/asf/zest-java/tree/96080d0a
Diff: http://git-wip-us.apache.org/repos/asf/zest-java/diff/96080d0a

Branch: refs/heads/develop
Commit: 96080d0ac4cb948c4890964fa64040586d337d9b
Parents: 7ba5335
Author: Paul Merlin <pa...@apache.org>
Authored: Sat Dec 3 14:40:49 2016 +0100
Committer: Paul Merlin <pa...@apache.org>
Committed: Sat Dec 3 15:51:53 2016 +0100

----------------------------------------------------------------------
 .../zest/runtime/structure/TypeLookupImpl.java  | 194 ++++++++++---------
 1 file changed, 107 insertions(+), 87 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zest-java/blob/96080d0a/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookupImpl.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookupImpl.java b/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookupImpl.java
index 5eac131..a030a20 100644
--- a/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookupImpl.java
+++ b/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookupImpl.java
@@ -153,20 +153,26 @@ class TypeLookupImpl
     @Override
     public List<EntityDescriptor> lookupEntityModels( final Class type )
     {
-        return entityModels.computeIfAbsent( type, key -> new TypeMatchingDescriptors<EntityDescriptor>(key).selectedFrom(allEntities()));
+        return entityModels.computeIfAbsent(
+            type,
+            key -> new TypeMatchesSelector<EntityDescriptor>( key ).selectFrom( allEntities() ) );
     }
 
     @Override
     public ModelDescriptor lookupServiceModel( Type serviceType )
     {
-        return serviceModels.computeIfAbsent( serviceType,
-                                              key -> new BestTypeMatchingDescriptors<ModelDescriptor>(key).selectedFrom(allServices()).bestMatchOrElse(null));
+        return serviceModels.computeIfAbsent(
+            serviceType,
+            key -> new BestTypeMatchSelector<ModelDescriptor>( key ).selectFrom( allServices() )
+                                                                    .bestMatchOrElse( null ) );
     }
 
     @Override
-    public List<? extends ModelDescriptor> lookupServiceModels( final Type type1 )
+    public List<? extends ModelDescriptor> lookupServiceModels( final Type type )
     {
-        return servicesReferences.computeIfAbsent( type1, type ->new TypeMatchingDescriptors<ModelDescriptor>(type).selectedFrom(allServices()));
+        return servicesReferences.computeIfAbsent(
+            type,
+            key -> new TypeMatchesSelector<ModelDescriptor>( key ).selectFrom( allServices() ) );
     }
 
     @Override
@@ -400,6 +406,102 @@ class TypeLookupImpl
     }
 
     /**
+     * Selects descriptors by combining {@link ExactTypeMatching} and {@link AssignableFromTypeMatching}.
+     *
+     * Selected descriptors are sorted, exact matches first, assignable ones second.
+     * Other than that, original order is preserved.
+     *
+     * <code>
+     *     [ assignable1, matching1, assignable2, assignable3, matching2, non-matching-nor-assignable ]
+     * </code>
+     * results in
+     * <code>
+     *     [ matching1, matching2, assignable1, assignable2, assignable3 ]
+     * </code>
+     *
+     * @param <T> Descriptor type
+     */
+    private static class TypeMatchesSelector<T extends HasTypes> extends ArrayList<T>
+    {
+        private final ExactTypeMatching<T> exactMatchPredicate;
+        private final AssignableFromTypeMatching<T> assignablePredicate;
+        private Integer lastMatchIndex;
+
+        private TypeMatchesSelector( Type type )
+        {
+            this.exactMatchPredicate = new ExactTypeMatching<>( type );
+            this.assignablePredicate = new AssignableFromTypeMatching<>( type );
+        }
+
+        List<T> selectFrom( Stream<? extends T> candidates )
+        {
+            candidates.forEach( this::addDescriptor );
+            return this;
+        }
+
+        private void addDescriptor( T descriptor )
+        {
+            if( contains( descriptor ) )
+            {
+                return;
+            }
+            if( exactMatchPredicate.test( descriptor ) )
+            {
+                Integer nextMatchIndex = lastMatchIndex == null ? 0 : lastMatchIndex + 1;
+                add( nextMatchIndex, descriptor );
+                lastMatchIndex = nextMatchIndex;
+            }
+            else if( assignablePredicate.test( descriptor ) )
+            {
+                add( descriptor );
+            }
+        }
+
+        boolean containsExactMatches()
+        {
+            return lastMatchIndex != null;
+        }
+    }
+
+    /**
+     * Selects the best matching descriptor by combining {@link ExactTypeMatching} and {@link AssignableFromTypeMatching}.
+     *
+     * Selected descriptor is the first exact match if it exists, the first assignable otherwise.
+     *
+     * @param <T> Descriptor type
+     */
+    private static class BestTypeMatchSelector<T extends HasTypes>
+    {
+        private TypeMatchesSelector<T> descriptors;
+
+        BestTypeMatchSelector( Type type )
+        {
+            this.descriptors = new TypeMatchesSelector<>( type );
+        }
+
+        BestTypeMatchSelector<T> selectFrom( Stream<? extends T> candidates )
+        {
+            candidates.forEach( this::addDescriptor );
+            return this;
+        }
+
+        T bestMatchOrElse( T or )
+        {
+            return !descriptors.isEmpty() ? descriptors.get( 0 ) : or;
+        }
+
+        private void addDescriptor( T descriptor )
+        {
+            // Until an exact match is found, even if we already found assignable ones,
+            // keep selecting in case the last element is an exact match.
+            if( !descriptors.containsExactMatches() )
+            {
+                descriptors.addDescriptor( descriptor );
+            }
+        }
+    }
+
+    /**
      * This Predicate will filter out all Models that doesn't have the same visibility as the first one.
      */
     private static class SameVisibility<T extends ModelDescriptor>
@@ -438,86 +540,4 @@ class TypeLookupImpl
             return value;
         }
     }
-    
-    private static class TypeMatchingDescriptors<T extends HasTypes> extends ArrayList<T> {
-
-        /**
-         * mutable :-( But performance is an issue here.
-         */
-        private Integer lastMatchingindex;
-        private final ExactTypeMatching<T> exactMatchingPredicate;
-        private final AssignableFromTypeMatching<T> assignablePredicate;
-
-        private TypeMatchingDescriptors(Type type) {
-            this.lastMatchingindex = null;
-            this.exactMatchingPredicate = new ExactTypeMatching<>(type);
-            this.assignablePredicate = new AssignableFromTypeMatching<>(type);
-        }
-
-        TypeMatchingDescriptors<T> selectedFrom(Stream<? extends T> candidates){
-            candidates.forEach(this::smartAddition);
-            return this;
-        }
-        /**
-         * Sorts the descriptors in a common list : matching ones first,
-         * assignable ones follow. The order of arrival is important :
-         * 
-         * "{assignable1, matching1, assignable2,assignable3,matching2,
-         * non-matching-or-assignable}" should result in "{ matching1,
-         * matching2, assignable1, assignable2, assignable3}"
-         */
-        private  void smartAddition(T descriptor) {
-            if (contains(descriptor)) {
-                return;
-            }
-            if ( exactMatchingPredicate.test(descriptor)) {
-                Integer nextMatchingIdx = lastMatchingindex == null ? 0 : lastMatchingindex + 1;
-                add(nextMatchingIdx, descriptor);
-                lastMatchingindex = nextMatchingIdx;
-                return;
-            }
-            if (assignablePredicate.test(descriptor)) {
-                add(descriptor);
-            }
-        }
-
-        private  boolean containsExactMatches() {
-            return lastMatchingindex != null;
-        }
-
-    }
-
-    private static class BestTypeMatchingDescriptors<T extends HasTypes> {
-
-        private TypeMatchingDescriptors<T> descriptors;
-
-        private  BestTypeMatchingDescriptors(Type type) {
-            this(new TypeMatchingDescriptors<>(type));
-        }
-
-        private  BestTypeMatchingDescriptors(TypeMatchingDescriptors<T> descriptors) {
-            this.descriptors = descriptors;
-        }
-
-        BestTypeMatchingDescriptors<T> selectedFrom(Stream<? extends T> candidates) {
-            candidates.forEach(this::smartAddition);
-            return this;
-        }
-        
-        private T bestMatchOrElse(T or) {
-            return !descriptors.isEmpty() ? descriptors.get(0) : or;
-        }
-
-        /**
-         * We want the first matching if exists, the first assignable otherwise.
-         * While there is no matching descriptor, even if we found assignable
-         * ones, we keep searching in case the last element is a matching type.
-         */
-        private void smartAddition(T descriptor) {
-            if (!descriptors.containsExactMatches()) {
-                descriptors.smartAddition(descriptor);
-            }
-        }
-    }
-
 }