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

[Skylark] Use POJOs instead of dynamic proxies. #5666

Closed
wants to merge 1 commit into from

Conversation

ttsugriy
Copy link
Contributor

Java uses dynamically generated proxy classes to access annotation properties
and their methods are ~7X slower than plain getters. According to async-profiler
50%+ of convertArgumentList method time is spent in dynamic proxy methods, so
optimizing their performance makes sense.

This also makes the model less anemic, since POJOs can actually provide business
methods.

@ttsugriy
Copy link
Contributor Author

proof of proxy vs getter perf - https://gist.github.com/ttsugriy/c5c7719b9d7bb993d62f445a0be94e5c

@ttsugriy
Copy link
Contributor Author

@laurentlb, @dslomov, would you mind taking a look? On top of having ~25% parse time improvement this change also enables further optimizations - I'll move the getType computation to a ParamDescriptor.of static factory method which should eliminate the next bottleneck - going through loading cache to resolve types, since types have to be resolved for every single method invocation and for each parameter.

@laurentlb laurentlb requested a review from c-parsons July 25, 2018 15:51
Copy link
Contributor

@c-parsons c-parsons left a comment

Choose a reason for hiding this comment

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

Just yesterday I was looking into doing something similar, but did not find that it had a substantial improvement on analysis time of large builds -- did you truly find a 25% improvement for this change? (How large is the build of concern, out of curiosity?)

}

/** A value class for storing {@link Param} metadata to avoid using Java proxies. */
public static final class ParamDescriptor {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please move this class to the top level? Long class definitions, even if they're trivial, make reading the containing class a bit more daunting.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

absolutely, I've used a nested class because MethodDescriptor is nested, but given how big all of these classes are, I agree that separating them makes code more readable :)

/**
* A value class to store Methods with their corresponding SkylarkCallable annotations.
* This is needed because the annotation is sometimes in a superclass.
* A value class to store Methods with their corresponding SkylarkCallable annotations. This is
Copy link
Contributor

Choose a reason for hiding this comment

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

Update javadoc

Does this now contain all of the information that was before stored in SkylarkCallable? Then we can remove that field

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does, but unfortunately getAnnotation is still used in SkylarkSignatureProcessor :(

throw new RuntimeException(String.format(
"Exception while processing @SkylarkSignature.Param %s, default value %s",
param.name(), param.defaultValue()), e);
if (defaultValue.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the change here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's so that this method can be used by clients that have ParamDescriptor or Param annotation. It's somewhat unfortunate since having parameters of the same type is a bit confusing, but until all usages of annotations are removed this is the way to avoid duplication :(

Copy link
Contributor

Choose a reason for hiding this comment

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

Apologies for the merge conflict here with dc6f1cb, (I had it pending before I saw your PR, and it just got its review stamped)
In brief, I found another large source of performance suffering in that we evaluate default values repeatedly throughout function invocations. Spinning up and evaluating a new AST is expensive, so caching the values is best.
The merge shouldn't be too bad.

It does make me wonder, however, if there are confounding factors with your ~50% proxy methods citation. (In truth, I'm more trying to reconcile my previous findings that proxy methods had little to no impact). One thing I did notice is that Param->SkylarkType conversion was expensive, and both my commit and yours resolve that.

Regardless, your change LGTM (after resolving merge conflicts), so this is mostly an aside.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no problem at all, @c-parsons! Thanks for working on these changes! The only explanation I can think of with regards to your proxy overhead observation is that in the code you have used for benchmarking you didn't have a lot of parameters/methods. Since Buck does not have extension support, we end up having extremely complicated macros with hundreds (200+) parameters, so for us it ends up using a lot of cycles :( I'll gladly rebase my changes. Thanks again for working on Skylark perf and your reviews!

Copy link
Contributor Author

@ttsugriy ttsugriy Jul 25, 2018

Choose a reason for hiding this comment

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

btw, @c-parsons, what do you think about moving the parsed defaultValue to the ParamDescriptor? At the moment a lot of time is spent in a LoadingCache, so storing the variable directly next to a parameter can make for a potentially easier to use API and better for performance. If you don't mind, I'll be happy to do this.

Copy link
Contributor Author

@ttsugriy ttsugriy left a comment

Choose a reason for hiding this comment

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

thanks for taking a look, @c-parsons! I have indeed observed a very noticeable (25+%) parse time improvement for our internal android apps and these proxy invocations have totally disappeared in profiler :) If you're curious I can also share async-profiler samples before and after this change :)

throw new RuntimeException(String.format(
"Exception while processing @SkylarkSignature.Param %s, default value %s",
param.name(), param.defaultValue()), e);
if (defaultValue.isEmpty()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's so that this method can be used by clients that have ParamDescriptor or Param annotation. It's somewhat unfortunate since having parameters of the same type is a bit confusing, but until all usages of annotations are removed this is the way to avoid duplication :(

@laurentlb
Copy link
Contributor

cc @meisterT

@ttsugriy ttsugriy force-pushed the no-callable-proxy branch 2 times, most recently from 8c18f12 to f7a902b Compare July 25, 2018 23:02
@ttsugriy
Copy link
Contributor Author

@c-parsons, I've effectively merged the getType caching from my other PR into this one, so please take a look at this one and I'll close the other one :)

Copy link
Contributor

@c-parsons c-parsons left a comment

Choose a reason for hiding this comment

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

Looks great. Thanks so much for this! I have just two nits to pick, but I'm approving :)

this.named = named;
this.legacyNamed = legacyNamed;
this.positional = positional;

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: kill extra newline

* metadata. This is needed because the annotation is sometimes in a superclass.
*
* <p>The annotation metadata is duplicated in this class to avoid usage of Java dynamic proxies
* which are ~7X slower
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: period at end of javadoc

Java uses dynamically generated proxy classes to access annotation properties
and their methods are ~7X slower than plain getters. According to async-profiler
50%+ of `convertArgumentList` method time is spent in dynamic proxy methods, so
optimizing their performance makes sense.

This also makes the model less anemic, since POJOs can actually provide business
methods.
@ttsugriy
Copy link
Contributor Author

Thanks for the supersonic review, @c-parsons! I've addressed the nits, so please take another look when you have a chance :)

@laurentlb
Copy link
Contributor

I made small changes when importing your PR to fix lint issues (copyright header, unused fields, functions with same name should be grouped).

@ttsugriy
Copy link
Contributor Author

Thank you, @laurentlb!

werkt pushed a commit to werkt/bazel that referenced this pull request Aug 2, 2018
Java uses dynamically generated proxy classes to access annotation properties
and their methods are ~7X slower than plain getters. According to async-profiler
50%+ of `convertArgumentList` method time is spent in dynamic proxy methods, so
optimizing their performance makes sense.

This also makes the model less anemic, since POJOs can actually provide business
methods.

Closes bazelbuild#5666.

PiperOrigin-RevId: 206608812
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants