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

perf: add heap benchmark and reduce allocations #1156

Merged
merged 10 commits into from
Oct 15, 2024
Merged

perf: add heap benchmark and reduce allocations #1156

merged 10 commits into from
Oct 15, 2024

Conversation

toddbaert
Copy link
Member

@toddbaert toddbaert commented Oct 10, 2024

This PR reduces allocations of heap objects by ~70% for most use-cases. It does this by removing some usage of the streams API in some hotspots, and also being more efficient with HashMaps and context merging.

Benchmark results before/after these changes:

Benchmark                                         Mode  Cnt          Score   Error      Units
AllocationBenchmark.run                             ss               0.171               s/op
AllocationBenchmark.run:+totalAllocatedBytes        ss       440750800.000              bytes
AllocationBenchmark.run:+totalAllocatedInstances    ss        12086130.000          instances
AllocationBenchmark.run:+totalHeap                  ss       521412608.000  
Benchmark                                         Mode  Cnt          Score   Error      Units
AllocationBenchmark.run                             ss               0.116               s/op
AllocationBenchmark.run:+totalAllocatedBytes        ss       138760912.000              bytes
AllocationBenchmark.run:+totalAllocatedInstances    ss         5005903.000          instances
AllocationBenchmark.run:+totalHeap                  ss       521412608.000              bytes

The performance optimizations are not very cool or exciting; mostly try to avoid creating unnecessary HashMaps and don't use streams in hot paths (like context merging in hooks).

@toddbaert toddbaert requested a review from a team as a code owner October 10, 2024 14:46
@toddbaert toddbaert changed the title chore: add heap benchmark and reduce allocations perf: add heap benchmark and reduce allocations Oct 10, 2024
Copy link

codecov bot commented Oct 10, 2024

Codecov Report

Attention: Patch coverage is 85.41667% with 7 lines in your changes missing coverage. Please review.

Project coverage is 93.20%. Comparing base (b144763) to head (c81c7d6).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/main/java/dev/openfeature/sdk/Structure.java 0.00% 2 Missing and 2 partials ⚠️
.../main/java/dev/openfeature/sdk/MutableContext.java 50.00% 1 Missing and 1 partial ⚠️
...in/java/dev/openfeature/sdk/AbstractStructure.java 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #1156      +/-   ##
============================================
- Coverage     93.71%   93.20%   -0.52%     
- Complexity      429      437       +8     
============================================
  Files            40       40              
  Lines          1019     1016       -3     
  Branches         72       84      +12     
============================================
- Hits            955      947       -8     
- Misses           40       42       +2     
- Partials         24       27       +3     
Flag Coverage Δ
unittests 93.20% <85.41%> (-0.52%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
@@ -0,0 +1,239 @@
[INFO] Scanning for projects...
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 don't care if we commit this or not, but I think we should for records.

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Comment on lines 41 to 46
protected Map<String, Value> getAttributes() {
if (attributes == null) {
attributes = new HashMap<>();
}
return attributes;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

perf: lazily initialize.

Copy link
Member

Choose a reason for hiding this comment

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

Makes it non thread safe?

Copy link
Member Author

Choose a reason for hiding this comment

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

Excellent point. I've reverted this because it's not a even a substantial savings. The bench result barely changes.

Comment on lines +43 to +47
for (Hook hook : hooks) {
if (hook.supportsFlagValueType(flagValueType)) {
executeChecked(hook, hookCode, hookMethod);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

perf: streams 😢

Comment on lines +66 to +70
for (Hook hook : hooks) {
if (hook.supportsFlagValueType(flagValueType)) {
hookCode.accept(hook);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

perf: streams 😢

Comment on lines +77 to +89
List<Hook> reversedHooks = new ArrayList<>(hooks);
Collections.reverse(reversedHooks);
EvaluationContext context = hookCtx.getCtx();
for (Hook hook : reversedHooks) {
if (hook.supportsFlagValueType(flagValueType)) {
Optional<EvaluationContext> optional = Optional.ofNullable(hook.before(hookCtx, hints))
.orElse(Optional.empty());
if (optional.isPresent()) {
context = context.merge(optional.get());
}
}
}
return context;
Copy link
Member Author

Choose a reason for hiding this comment

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

perf: streams 😢

Comment on lines +64 to +71
private static Map<String, Value> copyAttributes(Map<String, Value> in) {
Map<String, Value> copy = new HashMap<>();
for (Entry<String, Value> entry : in.entrySet()) {
copy.put(entry.getKey(),
Optional.ofNullable(entry.getValue()).map((Value val) -> val.clone()).orElse(null));
}
return copy;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

perf: common, non-stream method to copy.

Comment on lines +117 to +121
if (overridingContext == null || overridingContext.isEmpty()) {
return this;
}
if (this.isEmpty()) {
return overridingContext;
Copy link
Member Author

Choose a reason for hiding this comment

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

perf: quit early with less HashMap instantiations

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

AbstractStructure() {
this.attributes = new HashMap<>();
// intentionally don't initialize the attributes - do this lazily
Copy link
Member

Choose a reason for hiding this comment

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

Opinion: non-major performance improvement vs. less code later, less readable and possibly less bug prone in fututre?
Some references, from first search results:
1
2
3
4

Copy link
Member Author

Choose a reason for hiding this comment

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

Reverted for: #1156 (comment)

Comment on lines 41 to 46
protected Map<String, Value> getAttributes() {
if (attributes == null) {
attributes = new HashMap<>();
}
return attributes;
}
Copy link
Member

Choose a reason for hiding this comment

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

Makes it non thread safe?

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Copy link

sonarcloud bot commented Oct 15, 2024

@toddbaert toddbaert merged commit 9008818 into main Oct 15, 2024
6 of 10 checks passed
@toddbaert toddbaert deleted the jmh branch October 15, 2024 13:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants