-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Conversation
proof of proxy vs getter perf - https://gist.github.com/ttsugriy/c5c7719b9d7bb993d62f445a0be94e5c |
@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 |
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the change here?
There was a problem hiding this comment.
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 :(
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
5a5be69
to
c8afda8
Compare
There was a problem hiding this 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()) { |
There was a problem hiding this comment.
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 :(
c8afda8
to
83b3ba3
Compare
cc @meisterT |
8c18f12
to
f7a902b
Compare
@c-parsons, I've effectively merged the |
f7a902b
to
8c7dcdb
Compare
There was a problem hiding this 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; | ||
|
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
8c7dcdb
to
4e1df17
Compare
Thanks for the supersonic review, @c-parsons! I've addressed the nits, so please take another look when you have a chance :) |
I made small changes when importing your PR to fix lint issues (copyright header, unused fields, functions with same name should be grouped). |
Thank you, @laurentlb! |
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
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, sooptimizing their performance makes sense.
This also makes the model less anemic, since POJOs can actually provide business
methods.