assertj: 3.9.1 has bad performance regression

Summary

After upgrading to 3.9.1, I notice a regression in term of performance. By digging down, I notice that it spends a lot of time in the following code

  public static TypeComparators defaultTypeComparators() {
    TypeComparators comparatorByType = new TypeComparators();
    comparatorByType.put(Double.class, new DoubleComparator(DOUBLE_COMPARATOR_PRECISION));
    comparatorByType.put(Float.class, new FloatComparator(FLOAT_COMPARATOR_PRECISION));
    return comparatorByType;
  }

It sounds like it performs too much memory allocation and reflection cost on the following stack trace.

java.lang.Class.getEnclosingMethod0() Class.java (native)
java.lang.Class.getEnclosingMethodInfo() Class.java:1072
java.lang.Class.getEnclosingClass() Class.java:1272
java.lang.Class.getSimpleBinaryName() Class.java:1443
java.lang.Class.getSimpleName() Class.java:1309
org.assertj.core.internal.TypeComparators$$Lambda$77.apply(Object)
java.util.Comparator.lambda$comparing$77a9974f$1(Function, Object, Object) Comparator.java:469
java.util.Comparator$$Lambda$78.compare(Object, Object)
java.util.TreeMap.compare(Object, Object) TreeMap.java:1295
java.util.TreeMap.put(Object, Object) TreeMap.java:538
org.assertj.core.internal.TypeComparators.put(Class, Comparator) TypeComparators.java:99
org.assertj.core.internal.TypeComparators.defaultTypeComparators() TypeComparators.java:48
org.assertj.core.api.AbstractIterableAssert.<init>(Iterable, Class) AbstractIterableAssert.java:112
org.assertj.core.api.FactoryBasedNavigableIterableAssert.<init>(Iterable, Class, AssertFactory) FactoryBasedNavigableIterableAssert.java:32
org.assertj.core.api.IterableAssert.<init>(Iterable) IterableAssert.java:49
org.assertj.core.api.Assertions.assertThat(Iterable) Assertions.java:2586

This additional overhead is caused by the change from HashMap to TreeMap and the use of Comparator.comparing(Class::getSimpleName)

assertj 3.8.0 does not have this problem.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 63 (57 by maintainers)

Most upvoted comments

I understand. We have 2k unit tests, all using JUnitSoftAssertions rule. I’m not sure I can isolate/reproduce the issue in a single test file, let see what I can do.

I have done some more profiling for the assertj use case, could you try Byte Buddy 1.8.10?

So if I understand correctly it is safe to make new ByteBuddy() a field in SoftProxies instead of creating one everytime in:

private <V> Class<?> createProxy(Class<V> assertClass, ErrorCollector collector) {
    return new ByteBuddy().subclass(assertClass)
                          .method(METHODS_CHANGING_THE_OBJECT_UNDER_TEST)
                          .intercept(MethodDelegation.to(new ProxifyMethodChangingTheObjectUnderTest(this)))
                          .method(ElementMatchers.<MethodDescription> any()
                                                 .and(not(METHODS_CHANGING_THE_OBJECT_UNDER_TEST))
                                                 .and(not(methodsNotToProxy)))
                          .intercept(MethodDelegation.to(collector))
                          .make()
                          .load(assertClass.getClassLoader())
                          .getLoaded();

I’ll add .with(TypeValidation.DISABLED) thanks for the tip.

Let me take a stab at it

I use a lot of Assertions.assertThat in a tight loop. It pretty much consumes almost 100% of the cpu within the loop. I would suggest that you don’t even get TypeComparators.defaultTypeComparators() at all. You should have a constant copy of that map and use it if the assert does not override that.