Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iterate type interfaces without materialising a list during super type matching #1578

Merged

Conversation

richardstartin
Copy link
Member

@richardstartin richardstartin commented Jun 12, 2020

reduces allocation during type matching at startup.

Evaluation:

run spring pet-clinic with -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -mx1G -XX:+HeapDumpOnOutOfMemoryError to capture every allocated object in the first GB allocated. Results in reduction in ArrayList allocations by 1% of total (4th most commonly allocated object during startup)

before:
java.util.ArrayList           636,997 (4.1%)     20,383,904 B (1.6%)
after:
java.util.ArrayList           484,917   (3%)     15,517,344 B (1.2%)

Some tests weren't actually testing handling of an exception thrown during iteration, but simulated this by throwing on the getInterfaces() call - I fixed those.

@richardstartin richardstartin requested a review from a team as a code owner June 12, 2020 07:56
@richardstartin richardstartin force-pushed the richardstartin/reduce-startup-arraylist-allocation branch from 4ae4e0a to a550113 Compare June 12, 2020 08:14
Copy link
Contributor

@jbachorik jbachorik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice improvement - it is even better in relative percentage (from 4% to 3% is 25% save).

I have a few comments - mostly clarification questions and nits but there is one thing I am a bit concerned in SafeInterfaceIterator.

if (!checkedInterfaces.contains(type)) {
checkedInterfaces.add(type);
if (checkedInterfaces.add(type)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

@Override
public void remove() {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about throwing UnsupportedOperationException to make it totally clear that this method is not supposed to be called at all.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@richardstartin richardstartin force-pushed the richardstartin/reduce-startup-arraylist-allocation branch from a550113 to 6801e71 Compare June 12, 2020 08:54
@richardstartin
Copy link
Member Author

Regarding percentage, I think percentage of total is the number to focus on because we happen to allocate four com.squareup.moshi.Moshi$Lookup instances; eliminating one of those wouldn't be quite so significant.

@richardstartin richardstartin force-pushed the richardstartin/reduce-startup-arraylist-allocation branch from 6801e71 to b2bd5e1 Compare June 12, 2020 09:08
@richardstartin
Copy link
Member Author

This provokes an interesting failure detected by the netty tests:

java.lang.IllegalStateException: Cannot resolve type description for io.netty.util.internal.__matchers__.java.lang.ObjectMatcher
	at net.bytebuddy.pool.TypePool$Resolution$Illegal.resolve(TypePool.java:157)
	at datadog.trace.agent.tooling.bytebuddy.DDCachingPoolStrategy$CachingResolution.resolve(DDCachingPoolStrategy.java:270)
	at net.bytebuddy.pool.TypePool$Default$WithLazyResolution$LazyTypeDescription.delegate(TypePool.java:912)
	at net.bytebuddy.description.type.TypeDescription$AbstractBase$OfSimpleType$WithDelegation.getModifiers(TypeDescription.java:8331)
	at net.bytebuddy.matcher.ModifierMatcher.matches(ModifierMatcher.java:48)
	at net.bytebuddy.matcher.ModifierMatcher.matches(ModifierMatcher.java:27)
	at net.bytebuddy.matcher.NegatingMatcher.matches(NegatingMatcher.java:46)
	at net.bytebuddy.matcher.ElementMatcher$Junction$Conjunction.matches(ElementMatcher.java:122)
	at datadog.trace.agent.tooling.bytebuddy.matcher.LoggingFailSafeMatcher.matches(LoggingFailSafeMatcher.java:44)
	at net.bytebuddy.agent.builder.AgentBuilder$RawMatcher$ForElementMatchers.matches(AgentBuilder.java:1299)
	at net.bytebuddy.agent.builder.AgentBuilder$RawMatcher$Conjunction.matches(AgentBuilder.java:1150)
	at net.bytebuddy.agent.builder.AgentBuilder$RawMatcher$Conjunction.matches(AgentBuilder.java:1150)
	at net.bytebuddy.agent.builder.AgentBuilder$RawMatcher$Conjunction.matches(AgentBuilder.java:1150)
	at net.bytebuddy.agent.builder.AgentBuilder$Default$Transformation$SimpleMatcher.matches(AgentBuilder.java:9853)
	at net.bytebuddy.agent.builder.AgentBuilder$RedefinitionStrategy$Collector.consider(AgentBuilder.java:6941)
	at net.bytebuddy.agent.builder.AgentBuilder$RedefinitionStrategy.apply(AgentBuilder.java:4821)
	at net.bytebuddy.agent.builder.AgentBuilder$Default.doInstall(AgentBuilder.java:9463)
	at net.bytebuddy.agent.builder.AgentBuilder$Default.installOn(AgentBuilder.java:9384)
	at net.bytebuddy.agent.builder.AgentBuilder$Default$Delegator.installOn(AgentBuilder.java:10986)
	at datadog.trace.agent.tooling.AgentInstaller.installBytebuddyAgent(AgentInstaller.java:110)

Why are we trying to resolve a type description for io.netty.util.internal.__matchers__.java.lang.ObjectMatcher?

*
* <p>The caller MUST call hasNext() before calling next().
*
* <p>This wrapper exists to allow getting interfaces even if the lookup on one fails.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that this comment and behavior is the same as the old one, but maybe clarify that it stops at the first exception.

@richardstartin richardstartin force-pushed the richardstartin/reduce-startup-arraylist-allocation branch from b2bd5e1 to b5ca36b Compare June 12, 2020 11:59
@dougqh
Copy link
Contributor

dougqh commented Jun 12, 2020

This provokes an interesting failure detected by the netty tests:

java.lang.IllegalStateException: Cannot resolve type description for io.netty.util.internal.__matchers__.java.lang.ObjectMatcher
	at net.bytebuddy.pool.TypePool$Resolution$Illegal.resolve(TypePool.java:157)
	at datadog.trace.agent.tooling.bytebuddy.DDCachingPoolStrategy$CachingResolution.resolve(DDCachingPoolStrategy.java:270)
	at net.bytebuddy.pool.TypePool$Default$WithLazyResolution$LazyTypeDescription.delegate(TypePool.java:912)
	at net.bytebuddy.description.type.TypeDescription$AbstractBase$OfSimpleType$WithDelegation.getModifiers(TypeDescription.java:8331)
	at net.bytebuddy.matcher.ModifierMatcher.matches(ModifierMatcher.java:48)
	at net.bytebuddy.matcher.ModifierMatcher.matches(ModifierMatcher.java:27)
	at net.bytebuddy.matcher.NegatingMatcher.matches(NegatingMatcher.java:46)
	at net.bytebuddy.matcher.ElementMatcher$Junction$Conjunction.matches(ElementMatcher.java:122)
	at datadog.trace.agent.tooling.bytebuddy.matcher.LoggingFailSafeMatcher.matches(LoggingFailSafeMatcher.java:44)
	at net.bytebuddy.agent.builder.AgentBuilder$RawMatcher$ForElementMatchers.matches(AgentBuilder.java:1299)
	at net.bytebuddy.agent.builder.AgentBuilder$RawMatcher$Conjunction.matches(AgentBuilder.java:1150)
	at net.bytebuddy.agent.builder.AgentBuilder$RawMatcher$Conjunction.matches(AgentBuilder.java:1150)
	at net.bytebuddy.agent.builder.AgentBuilder$RawMatcher$Conjunction.matches(AgentBuilder.java:1150)
	at net.bytebuddy.agent.builder.AgentBuilder$Default$Transformation$SimpleMatcher.matches(AgentBuilder.java:9853)
	at net.bytebuddy.agent.builder.AgentBuilder$RedefinitionStrategy$Collector.consider(AgentBuilder.java:6941)
	at net.bytebuddy.agent.builder.AgentBuilder$RedefinitionStrategy.apply(AgentBuilder.java:4821)
	at net.bytebuddy.agent.builder.AgentBuilder$Default.doInstall(AgentBuilder.java:9463)
	at net.bytebuddy.agent.builder.AgentBuilder$Default.installOn(AgentBuilder.java:9384)
	at net.bytebuddy.agent.builder.AgentBuilder$Default$Delegator.installOn(AgentBuilder.java:10986)
	at datadog.trace.agent.tooling.AgentInstaller.installBytebuddyAgent(AgentInstaller.java:110)

Why are we trying to resolve a type description for io.netty.util.internal.__matchers__.java.lang.ObjectMatcher?

The handling of this case does turn out to be important here. ByteBuddy first constructs a lazy TypeDescriptor without loading the underlying class file. If later, it needs to load the class file, but cannot -- then an exception is raised.

I'd previously done an experiment with creating a custom Iterator that catches the exception to avoid the extra ArrayList-s while handling the exception. At that the time, we were mostly focused on start-up, so I don't end up putting the change in.


@Override
public int compare(TypeDescription o1, TypeDescription o2) {
return o1.getSimpleName().compareTo(o2.getSimpleName());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to getName and the :dd-java-agent:instrumentation:netty-4.1:testJava11Generated succeeded locally, while getSimpleName fails.

    public int compare(TypeDescription o1, TypeDescription o2) {
      if (o1 == o2) {
        return 0;
      }

      return o1.getName().compareTo(o2.getName());
    }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is name sufficient here? We have to be a bit carefully about duplicates being loaded across different ClassLoaders, but in a limited context, name should work.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'll just change this back to HashSet to avoid edge cases.

Copy link
Contributor

@tylerbenson tylerbenson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice change!

@richardstartin richardstartin force-pushed the richardstartin/reduce-startup-arraylist-allocation branch from bf43a07 to e8302ef Compare June 12, 2020 15:13
@richardstartin richardstartin added tag: low hanging fruit Low hanging fruits tag: performance Performance related changes labels Jun 12, 2020
@richardstartin richardstartin changed the title Iterate type interfaces without materialisation during super type matching Iterate type interfaces without materialising a list during super type matching Jun 12, 2020
@richardstartin richardstartin removed the tag: low hanging fruit Low hanging fruits label Jun 12, 2020
@richardstartin richardstartin force-pushed the richardstartin/reduce-startup-arraylist-allocation branch from e8302ef to 9527c77 Compare June 12, 2020 15:36
@richardstartin richardstartin merged commit 0b935ed into master Jun 12, 2020
@richardstartin richardstartin deleted the richardstartin/reduce-startup-arraylist-allocation branch June 12, 2020 16:33
@github-actions github-actions bot added this to the 0.55.0 milestone Jun 12, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tag: performance Performance related changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants