From 1eb2ccccc61ef411057e94a096c4226f66f88271 Mon Sep 17 00:00:00 2001 From: Zsolt Herpai Date: Tue, 6 Oct 2015 13:29:06 +0200 Subject: [PATCH] Performance optimizations (most notably injectfields). Number of iterations increased in startup comparison test --- .../java/org/codejargon/feather/Feather.java | 261 ++++++++++++------ .../codejargon/feather/FeatherException.java | 4 +- .../org/codejargon/feather/InjectField.java | 15 + .../org/codejargon/feather/Inspection.java | 88 ------ .../feather/StartupComparisonTest.java | 4 +- ...ThroughputInstantiationComparisonTest.java | 68 ----- 6 files changed, 194 insertions(+), 246 deletions(-) create mode 100644 feather/src/main/java/org/codejargon/feather/InjectField.java delete mode 100644 feather/src/main/java/org/codejargon/feather/Inspection.java delete mode 100644 performance-test/src/test/java/org/codejargon/feather/ThroughputInstantiationComparisonTest.java diff --git a/feather/src/main/java/org/codejargon/feather/Feather.java b/feather/src/main/java/org/codejargon/feather/Feather.java index f277b98..2f58c92 100644 --- a/feather/src/main/java/org/codejargon/feather/Feather.java +++ b/feather/src/main/java/org/codejargon/feather/Feather.java @@ -10,6 +10,7 @@ public class Feather { private final Map> providers = new ConcurrentHashMap<>(); private final Map singletons = new ConcurrentHashMap<>(); private final Map[]> paramProviders = new ConcurrentHashMap<>(); + private final Map injectFields = new ConcurrentHashMap<>(0); /** * Constructs Feather with the provided configuration modules @@ -35,9 +36,9 @@ public Object get() { ); for (final Object module : modules) { if (module instanceof Class) { - throw new FeatherException(String.format("Class %s provided as module instead of an instance.", ((Class) module).getName())); + throw new FeatherException(String.format("%s provided as class instead of an instance.", ((Class) module).getName())); } - for (Method providerMethod : Inspection.providers(module.getClass())) { + for (Method providerMethod : providers(module.getClass())) { providerMethod(module, providerMethod); } } @@ -75,15 +76,14 @@ public Provider provider(Key key) { * Injects fields to the target object (non-transitive) */ public void injectFields(Object target) { - for (Field f : Inspection.injectFields(target.getClass())) { - Class providerType = f.getType().equals(Provider.class) ? - (Class) ((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0] : - null; - Key key = Key.of(providerType != null ? providerType : f.getType(), Inspection.qualifier(f.getAnnotations())); + if (!injectFields.containsKey(target.getClass())) { + injectFields.put(target.getClass(), injectFields(target.getClass())); + } + for (InjectField f : injectFields.get(target.getClass())) { try { - f.set(target, providerType != null ? provider(key) : instance(key)); + f.field.set(target, f.providerType ? provider(f.key) : instance(f.key)); } catch (IllegalAccessException e) { - throw new FeatherException(String.format("Can't inject field %s to an instance of %s", f.getName(), target.getClass().getName())); + throw new FeatherException(String.format("Can't inject to field %s in %s", f.field.getName(), target.getClass().getName())); } } } @@ -91,55 +91,43 @@ public void injectFields(Object target) { @SuppressWarnings("unchecked") private Provider provider(final Key key, Set depChain) { if (!providers.containsKey(key)) { - synchronized (providers) { - if (!providers.containsKey(key)) { - final Constructor constructor = Inspection.constructor(key); - final Provider[] paramProviders = providersForParams(key, constructor.getParameterTypes(), constructor.getGenericParameterTypes(), constructor.getParameterAnnotations(), depChain); - providers.put(key, singletonProvider(key, key.type.getAnnotation(Singleton.class), new Provider() { - @Override - public Object get() { - try { - return constructor.newInstance(arguments(paramProviders)); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new FeatherException(String.format("Failed to instantiate dependency %s", key.toString()), e); - } - } - }) - ); - } - } + final Constructor constructor = constructor(key); + final Provider[] paramProviders = providersForParams(key, constructor.getParameterTypes(), constructor.getGenericParameterTypes(), constructor.getParameterAnnotations(), depChain); + providers.put(key, singletonProvider(key, key.type.getAnnotation(Singleton.class), new Provider() { + @Override + public Object get() { + try { + return constructor.newInstance(arguments(paramProviders)); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new FeatherException(String.format("Can't instantiate dependency %s", key.toString()), e); + } + } + }) + ); } return (Provider) providers.get(key); } - private Object[] arguments(Provider[] paramProviders) { - Object[] args = new Object[paramProviders.length]; - for (int i = 0; i < paramProviders.length; ++i) { - args[i] = paramProviders[i].get(); - } - return args; - } - - private void circularCheck(Key key, Set depChain) { - if (depChain != null && depChain.contains(key)) { - throw new FeatherException(String.format("Circular dependency: %s", chainString(depChain, key))); - } - } - private void providerMethod(final Object module, final Method m) { - final Key key = Key.of(m.getReturnType(), Inspection.qualifier(m.getAnnotations())); + final Key key = Key.of(m.getReturnType(), qualifier(m.getAnnotations())); if (providers.containsKey(key)) { - throw new FeatherException(String.format("Multiple providers for dependency %s defined in module %s", key.toString(), module.getClass())); + throw new FeatherException(String.format("Multiple providers for dependency %s, in module %s", key.toString(), module.getClass())); } Singleton singleton = m.getAnnotation(Singleton.class) != null ? m.getAnnotation(Singleton.class) : m.getReturnType().getAnnotation(Singleton.class); - final Provider[] paramProviders = providersForParams(key, m.getParameterTypes(), m.getGenericParameterTypes(), m.getParameterAnnotations(), Collections.singleton(key)); + final Provider[] paramProviders = providersForParams( + key, + m.getParameterTypes(), + m.getGenericParameterTypes(), + m.getParameterAnnotations(), + Collections.singleton(key) + ); providers.put(key, singletonProvider(key, singleton, new Provider() { @Override public Object get() { try { return m.invoke(module, arguments(paramProviders)); } catch (IllegalAccessException | InvocationTargetException e) { - throw new FeatherException(String.format("Failed to instantiate %s with provider", key.toString()), e); + throw new FeatherException(String.format("Can't instantiate %s with provider", key.toString()), e); } } } @@ -164,47 +152,63 @@ public T get() { } : provider; } - private Provider[] providersForParams(final Key key, Class[] parameterClasses, Type[] parameterTypes, Annotation[][] parameterAnnotations, final Set depChain) { + private Provider[] providersForParams( + final Key key, + Class[] parameterClasses, + Type[] parameterTypes, + Annotation[][] parameterAnnotations, + final Set depChain + ) { if (!paramProviders.containsKey(key)) { - synchronized (paramProviders) { - if (!paramProviders.containsKey(key)) { - Provider[] providers = new Provider[parameterTypes.length]; - for(int i = 0; i < parameterTypes.length; ++i) { - Type parameterType = parameterTypes[i]; - Class parameterClass = parameterClasses[i]; - Annotation qualifier = Inspection.qualifier(parameterAnnotations[i]); - Class providerType = Provider.class.equals(parameterClass) ? - (Class)((ParameterizedType) parameterType).getActualTypeArguments()[0] : - null; - if(providerType == null) { - final Key newKey = Key.of(parameterClass, qualifier); - final Set dependencyChain = append(depChain, key); - circularCheck(newKey, dependencyChain); - providers[i] = new Provider() { - @Override - public Object get() { - return provider(newKey, dependencyChain).get(); - } - }; - } else { - final Key newKey = Key.of(providerType, qualifier); - providers[i] = new Provider() { - @Override - public Object get() { - return provider(newKey, null); - } - }; + Provider[] providers = new Provider[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; ++i) { + Type parameterType = parameterTypes[i]; + Class parameterClass = parameterClasses[i]; + Annotation qualifier = qualifier(parameterAnnotations[i]); + Class providerType = Provider.class.equals(parameterClass) ? + (Class) ((ParameterizedType) parameterType).getActualTypeArguments()[0] : + null; + if (providerType == null) { + final Key newKey = Key.of(parameterClass, qualifier); + final Set dependencyChain = append(depChain, key); + circularCheck(newKey, dependencyChain); + providers[i] = new Provider() { + @Override + public Object get() { + return provider(newKey, dependencyChain).get(); } - } - paramProviders.put(key, providers); + }; + } else { + final Key newKey = Key.of(providerType, qualifier); + providers[i] = new Provider() { + @Override + public Object get() { + return provider(newKey, null); + } + }; } } + paramProviders.put(key, providers); } return paramProviders.get(key); } - private Set append(Set set, Key newItem) { - if(set != null && !set.isEmpty()) { + private static Object[] arguments(Provider[] paramProviders) { + Object[] args = new Object[paramProviders.length]; + for (int i = 0; i < paramProviders.length; ++i) { + args[i] = paramProviders[i].get(); + } + return args; + } + + private static void circularCheck(Key key, Set depChain) { + if (depChain != null && depChain.contains(key)) { + throw new FeatherException(String.format("Circular dependency: %s", chainString(depChain, key))); + } + } + + private static Set append(Set set, Key newItem) { + if (set != null && !set.isEmpty()) { Set appended = new LinkedHashSet<>(set); appended.add(newItem); return appended; @@ -213,19 +217,104 @@ private Set append(Set set, Key newItem) { } } - static String chainString(Set chain, Key lastKey) { + private static InjectField[] injectFields(Class target) { + Set fields = fields(target); + InjectField[] injectFields = new InjectField[fields.size()]; + int i = 0; + for(Field f : fields) { + Class providerType = f.getType().equals(Provider.class) ? + (Class) ((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0] : + null; + injectFields[i++] = new InjectField( + f, + providerType != null, + Key.of(providerType != null ? providerType : f.getType(), qualifier(f.getAnnotations())) + ); + } + return injectFields; + } + + private static Set fields(Class target) { + Class currentClass = target; + Set fields = new HashSet<>(); + while (!currentClass.equals(Object.class)) { + for (Field field : currentClass.getDeclaredFields()) { + if (field.isAnnotationPresent(Inject.class)) { + field.setAccessible(true); + fields.add(field); + } + } + currentClass = currentClass.getSuperclass(); + } + return fields; + } + + private static String chainString(Set chain, Key lastKey) { List keys = new ArrayList<>(chain); keys.add(lastKey); - StringBuilder stringBuilder = new StringBuilder(); - boolean first = true; + StringBuilder chainString = new StringBuilder(); for (Key key : keys) { - if (first) { - stringBuilder.append(key.toString()); - first = false; - } else { - stringBuilder.append(" -> ").append(key.toString()); + chainString.append(" -> ").append(key.toString()); + } + return chainString.toString(); + } + + private static Constructor constructor(Key key) { + Constructor inject = null; + Constructor noarg = null; + for (Constructor c : key.type.getDeclaredConstructors()) { + if (c.isAnnotationPresent(Inject.class)) { + if (inject != null) { + throw new FeatherException(String.format("Dependency %s has more than one @Inject constructor", key.type)); + } else { + c.setAccessible(true); + inject = c; + + } + } else if (c.getParameters().length == 0) { + c.setAccessible(true); + noarg = c; + } + } + Constructor constructor = inject != null ? inject : noarg; + if (constructor != null) { + constructor.setAccessible(true); + return constructor; + } else { + throw new FeatherException(String.format("Dependency %s must have an @Inject constructor or a no-arg constructor or configured with @Provides in a module", key.type.getName())); + } + } + + private static Set providers(Class clazz) { + Class currentClass = clazz; + Set providerMethods = new HashSet<>(); + while (!currentClass.equals(Object.class)) { + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(Provides.class) && !providerInSubClass(method, providerMethods)) { + method.setAccessible(true); + providerMethods.add(method); + } + } + currentClass = currentClass.getSuperclass(); + } + return providerMethods; + } + + private static Annotation qualifier(Annotation[] annotations) { + for (Annotation annotation : annotations) { + if (annotation.annotationType().isAnnotationPresent(Qualifier.class)) { + return annotation; + } + } + return null; + } + + private static boolean providerInSubClass(Method method, Set discoveredMethods) { + for (Method discovered : discoveredMethods) { + if (discovered.getName().equals(method.getName()) && Arrays.equals(method.getParameterTypes(), discovered.getParameterTypes())) { + return true; } } - return stringBuilder.toString(); + return false; } } diff --git a/feather/src/main/java/org/codejargon/feather/FeatherException.java b/feather/src/main/java/org/codejargon/feather/FeatherException.java index bdee7d1..c52f1db 100644 --- a/feather/src/main/java/org/codejargon/feather/FeatherException.java +++ b/feather/src/main/java/org/codejargon/feather/FeatherException.java @@ -1,11 +1,11 @@ package org.codejargon.feather; public class FeatherException extends RuntimeException { - public FeatherException(String message) { + FeatherException(String message) { super(message); } - public FeatherException(String message, Throwable cause) { + FeatherException(String message, Throwable cause) { super(message, cause); } } diff --git a/feather/src/main/java/org/codejargon/feather/InjectField.java b/feather/src/main/java/org/codejargon/feather/InjectField.java new file mode 100644 index 0000000..86cae5d --- /dev/null +++ b/feather/src/main/java/org/codejargon/feather/InjectField.java @@ -0,0 +1,15 @@ +package org.codejargon.feather; + +import java.lang.reflect.Field; + +class InjectField { + final Field field; + final boolean providerType; + final Key key; + + InjectField(Field field, boolean providerType, Key key) { + this.field = field; + this.providerType = providerType; + this.key = key; + } +} diff --git a/feather/src/main/java/org/codejargon/feather/Inspection.java b/feather/src/main/java/org/codejargon/feather/Inspection.java deleted file mode 100644 index 84b33b7..0000000 --- a/feather/src/main/java/org/codejargon/feather/Inspection.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.codejargon.feather; - -import javax.inject.Inject; -import javax.inject.Qualifier; -import java.lang.annotation.Annotation; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -class Inspection { - public static Set providers(Class clazz) { - Class currentClass = clazz; - Set providerMethods = new HashSet<>(); - while (!currentClass.equals(Object.class)) { - for (Method method : clazz.getDeclaredMethods()) { - if (method.isAnnotationPresent(Provides.class) && !providerInSubClass(method, providerMethods)) { - method.setAccessible(true); - providerMethods.add(method); - } - } - currentClass = currentClass.getSuperclass(); - } - return providerMethods; - } - - public static Constructor constructor(Key key) { - Constructor inject = null; - Constructor noarg = null; - for (Constructor c : key.type.getDeclaredConstructors()) { - if (c.isAnnotationPresent(Inject.class)) { - if (inject != null) { - throw new FeatherException(String.format("Dependency %s has more than one @Inject constructor", key.type)); - } else { - c.setAccessible(true); - inject = c; - } - } else if (c.getParameters().length == 0) { - c.setAccessible(true); - noarg = c; - } - } - Constructor constructor = inject != null ? inject : noarg; - if (constructor != null) { - constructor.setAccessible(true); - return constructor; - } else { - throw new FeatherException(String.format("Dependency %s must have an @Inject constructor or a no-arg constructor or configured with @Provides in a module", key.type.getName())); - } - } - - public static Set injectFields(Class target) { - Class currentClass = target; - Set fields = new HashSet<>(); - while (!currentClass.equals(Object.class)) { - for (Field field : currentClass.getDeclaredFields()) { - if (field.isAnnotationPresent(Inject.class)) { - field.setAccessible(true); - fields.add(field); - } - } - currentClass = currentClass.getSuperclass(); - } - return fields; - } - - public static Annotation qualifier(Annotation[] annotations) { - for (Annotation annotation : annotations) { - if (annotation.annotationType().isAnnotationPresent(Qualifier.class)) { - return annotation; - } - } - return null; - } - - private static boolean providerInSubClass(Method method, Set discoveredMethods) { - for (Method discovered : discoveredMethods) { - if (discovered.getName().equals(method.getName()) && Arrays.equals(method.getParameterTypes(), discovered.getParameterTypes())) { - return true; - } - } - return false; - } - - -} diff --git a/performance-test/src/test/java/org/codejargon/feather/StartupComparisonTest.java b/performance-test/src/test/java/org/codejargon/feather/StartupComparisonTest.java index 782644a..244dbab 100644 --- a/performance-test/src/test/java/org/codejargon/feather/StartupComparisonTest.java +++ b/performance-test/src/test/java/org/codejargon/feather/StartupComparisonTest.java @@ -15,8 +15,8 @@ An iteration includes creating an injector and instantiating the dependency graph. */ public class StartupComparisonTest { - private static final int warmup = 100; - private static final int iterations = 10000; + private static final int warmup = 200; + private static final int iterations = 20000; @Test public void startupTime() { diff --git a/performance-test/src/test/java/org/codejargon/feather/ThroughputInstantiationComparisonTest.java b/performance-test/src/test/java/org/codejargon/feather/ThroughputInstantiationComparisonTest.java deleted file mode 100644 index 20a005a..0000000 --- a/performance-test/src/test/java/org/codejargon/feather/ThroughputInstantiationComparisonTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.codejargon.feather; - -import com.google.inject.Guice; -import com.google.inject.Injector; -import dagger.ObjectGraph; -import org.junit.Test; -import org.picocontainer.MutablePicoContainer; -import org.springframework.context.ApplicationContext; - -/** - Measures instantiation throughput of DI tools. - An iteration includes instantiating the dependency graph (injector created only once) - */ -public class ThroughputInstantiationComparisonTest { - private static final int warmup = 100; - private static final int iterations = 1000000; - - @Test - public void instantiationThroughput() { - System.out.println(String.format("Instantiating a dependency graph %s times", iterations)); - Feather feather = Feather.with(); - Injector injector = Guice.createInjector(); - MutablePicoContainer pico = StartupComparisonTest.pico(); - ObjectGraph dagger = StartupComparisonTest.dagger(); - ApplicationContext spring = StartupComparisonTest.spring(); - PojoFactory pojo = new PojoFactory(); - - for (int i = 0; i < warmup; ++i) { - pojo.create(); - feather.instance(A.class); - injector.getInstance(A.class); - pico.getComponent(A.class); - dagger.get(A.class); - spring.getBean(A.class); - } - - StopWatch.millis("Plain new", () -> { - for (int i = 0; i < iterations; ++i) { - pojo.create(); - } - }); - StopWatch.millis("Guice", () -> { - for (int i = 0; i < iterations; ++i) { - injector.getInstance(A.class); - } - }); - StopWatch.millis("Feather", () -> { - for (int i = 0; i < iterations; ++i) { - feather.instance(A.class); - } - }); - StopWatch.millis("Dagger", () -> { - for (int i = 0; i < iterations; ++i) { - dagger.get(A.class); - } - }); - StopWatch.millis("Spring", () -> { - for (int i = 0; i < iterations; ++i) { - spring.getBean(A.class); - } - }); - StopWatch.millis("PicoContainer", () -> { - for (int i = 0; i < iterations; ++i) { - pico.getComponent(A.class); - } - }); - } -}