-
Notifications
You must be signed in to change notification settings - Fork 3k
Micro allocations optimisation in ArC #25835
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
Conversation
76f2e36 to
6a061e5
Compare
independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextDataMap.java
Show resolved
Hide resolved
manovotn
left a comment
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.
@Sanne how often does Arc initialize the lazy map we've had before?
It should only happen if the interceptor accesses its bindings - is there some interceptor forcibly doing that for majority of cases?
I guess I am just trying to get the hang of why the previous lazy init was inefficient.
I understand you want to avoid init even in the case of bindings but I don't like that some methods are left out. Technically, that's against specification.
independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextDataMap.java
Show resolved
Hide resolved
| @Override | ||
| public void putAll(Map m) { | ||
| if (m.containsKey(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS)) { | ||
| throw new IllegalArgumentException("Not allowed to put key '" + ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); |
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: missing + "'" at the end, here and on 2 more places below.
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!
|
Disallowing calling some methods (and/or with some parameters) on the context data map is an interesting idea, as it protects interceptors from stepping on each others toes too much. Too bad the specification doesn't really say much about how this map should work. What's in this PR seems reasonable to me. |
Note that the specification says:
Hence it actually allows them to step on each others toes to some extent. |
|
Right, I mean, sure, the entire purpose of the context data map is for 2 interceptors to be able to exchange information. But they need to be aware of each other, which I assume typically means there's a fixed set of keys the 2 interceptors know and use. But if an interceptor tries to access the whole key set / value collection of the context data map, that is suspicious. |
|
Yes, but the spec doesn't limit that either. |
|
I understand and agree that the spec doesn't restrict the usage of the context data map, so there's a good argument to not restrict it on our side either. At the same time, I believe there's also a good argument to be made to restrict it, and the present PR does a decent job in my opinion. Obviously, Hyrum's law dictates that we shouldn't really change it now :-) |
Right, sorry I sent the draft in a rush and was supposed to give some better explanations after finishing tests. We were running performance tests again, and noticed a recurring pattern in interceptors; take The first lines of code start with: First thing it does is to try reading from the The purpose of this PR is to keep us from initializing the Map if all what's needed is read operations. Also: since the pattern I see in Also: this optimisation proposal doesn't have performance drawbacks for the write cases either - it just replaces the So I believe it provides a win-win situation, and optimised for the more realistic scenario of
Right - we can certainly implement the additional methods as well, but while looking into it I had a bad feeling about allowing that - for the same points @Ladicek made. Please think about it and let me know the vertict - I can certainly implement the missing capabilities so to only focus on the performance optimisation at this stage, but I'm inclined to think this approach is safer. I understand the purpose is for interceptors to "share" some state but I hope - for sake of our sanity - that even among spec writers there's an implied assumption that only one interceptor is the "owner" of a particular key, so while allowing others to read it, it shouldn't be allowed for a different one to write / replace / delete on a key space they don't own. |
|
I think most interceptors actually don't use the context data map at all, so the lazy allocation isn't as inefficient as you think. Unfortunately the "current" interceptor binding annotations are not exposed in a better way -- but in the CDI community, we're talking about exposing the interceptor binding annotations directly from the |
|
Thanks for more context Sanne, few more notes:
Where does this usage come from? I mean, which project uses the interceptor in question?
I won't hard press you for that; looks like I am the only one thinking they should be there :) |
True, most user-defined interceptors won't care much, this is more used by other frameworks that need to introspect values in their respective bindings. For instance JTA (Narayana) was using this particular feature (if memory serves) which basically means any operations with transactions will trigger it as well.
+1, I agree it makes sense to special-case this scenario. |
|
Thanks for all suggestions :) I've pushed a revised version - same basic principles but:
|
Right, this interceptor from CP is pulled in and activated by default by many Quarkus extensions by default. It's very likely that any application that has any reactive dependencies will have this interceptor on many endpoints, even when users didn't intentionally opt-in or are aware of this. So this is having wide impact. |
franz1981
left a comment
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.
LGTM!
|
|
||
| @Override | ||
| public Set<Map.Entry<String, Object>> entrySet() { | ||
| final AbstractMap.SimpleImmutableEntry<String, Object> firstEntry = new AbstractMap.SimpleImmutableEntry<>( |
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.
This can be lazily allocated (and reused) as well, given that users cannot modify it?
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.
yes it could - but I don't think it's worth it: hopefully nobody is using entrySet heavily on this map.
manovotn
left a comment
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 good
|
Technically if we really want the context data map to be a full map, then |
oh that would certainly be simpler. But are you saying that the interceptors entry doesn't need to be listed? And what about the
I could do that but it depends on the previous doubt - I think how we treat the "interceptors entry" needs to be consistent. |
|
ah sorry you were referring to fact one can modify the keyset and see changes reflected in the Map. Right I ignored that. |
mkouba
left a comment
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.
I like the idea in general but speaking of optimizations and SmallRyeCurrentThreadContextInterceptor it might be more effective to provide a quarkus-specific version of this interceptor that would simply use io.quarkus.arc.ArcInvocationContext#getInterceptorBindings() instead.
| } | ||
|
|
||
| @Override | ||
| public Collection values() { |
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.
Is there a reason to use so many raw types in this class? I get a lot of useless warnings in my IDE ;-)
I've omitted implementing some of the methods in the new Map implementation:
in part because it seemed unneccessary, but in part also because I feel it would be safer to not expose such operations.