-
Notifications
You must be signed in to change notification settings - Fork 1k
Avoid potential allocation from iterator empty check in Tags/KeyValues #6148
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
base: 1.13.x
Are you sure you want to change the base?
Conversation
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. I had in my notes to make this change, but I was curious to benchmark if there's a noticeable difference with this. I haven't had a chance yet to look into it more deeply, but on the surface it certainly seems worth avoiding the allocation if we can.
I still haven't had a chance to do the deeper dive I wanted on this and similar changes. I don't expect it would cause any problem, but I'll hold off on merging it until next release. |
This is going to clash with changes in #6185, but since this was opened first and has a narrower scope, I'll work on getting this merged first before dealing with that. I've been doing a lot of profiling and debugging. The iterator call can be inlined by C2 in my tests, but that doesn't mean we shouldn't make it better pre-C2 compilation. An interesting scenario that comes up usually during application startup that may be worse with this change is when the argument is I've added unit tests that assert no allocations in the cases we don't expect it. This will protect us from future changes and make the expectation clear. I've also made the same changes for KeyValues, and this same change for |
@Test | ||
@DisabledIfSystemProperty(named = "java.vm.name", matches = JAVA_VM_NAME_J9_REGEX, | ||
disabledReason = "Sun ThreadMXBean with allocation counter not available") | ||
@EnabledIf("java19") |
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 got rid of the Java 19 specific test here because we don't build with Java 19 and it's generally out of support as a non-LTS version. I think I already removed it from Tags in another change.
@MethodSource("nonEmptyKeyValues_noAllocationArgs") | ||
@MethodSource("emptyNull_noAllocationArgs") | ||
void of_noAllocation(Iterable<KeyValue> arg) { | ||
assertNoAllocations(() -> KeyValues.of(arg)); |
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 don't know how readable and clear this way of writing the tests is. I spent quite a bit of time trying different ways of writing these tests and this is what I ended up on. I made an @AllocationTest
annotation to not need to duplicate the logic of skipping on J9. I made them parameterized because for each there are a number of different inputs we expect should not allocate. I tried to avoid duplication by combining method sources. Good naming was hard to come by. I'm happy to hear thoughts on ways to improve this, though it's probably a bikeshed topic.
In earlier versions, I had multiple assert calls in a test for each of the inputs for each of the tests, but then inputs need to be duplicated between tests. That way it is easier to see from reading the code what cases are being tested where, but then it's easy to miss adding/changing an input in a related test, I felt. I'm not entirely convinced the current setup at the time of this comment (with ParameterizedTest and MethodSource) is better than that earlier version, though. I even explored using something like @CartesianTest
but abandoned it as overkill for this.
I'm out of time for now, but I'll run some JMH tests later and hopefully get this merged soon. Feedback welcome in the meantime. |
I added some benchmarks since the previous ones were only testing one of the Tags.of and Tags.and variants (the String varargs one, which we didn't change). The results were mostly what we expected: the changes don't make a difference in allocation when the C2 compiler eliminates the iterator allocation. If we run the benchmarks with I expected the results for The logs give the reason for inlining failure as I'm not sure it's worth investing more time into these changes unless we can better understand what's happening and why. I'll leave the PR open for now, but these results make me in favor of not merging the changes. If anyone else has a hypothesis to test or can educate me on this, feel free to help. Edit: for added context, I ran the benchmarks with OpenJDK (Liberica) 21.0.4 and 24.0.1. I saw some C2 compiler bugs were fixed that seemed potentially related, but those fixes should have been included in 24.0.1, yet I still was able to intermittently reproduce the failure to inline. |
This PR changes to check
Tags
instanceof
check before iterator empty check inTags.of()
to saveArrayIterator
creation.