-
Notifications
You must be signed in to change notification settings - Fork 10
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
Nested metric registries don't cause a sortedmap creation #430
Conversation
This might seem like an unnecessary optimization, but profiling internally implies this uses plenty of CPU and allocates gigabytes and gigabytes and gigabytes (about 1.8GB of allocations to report metrics). BLUF: With nested tags, our tooling spends all of its time recreating nested sorted maps and rehashing, which is very expensive. That said, breaking binary backcompat here is super expensive, as we know from past experience. This PR solves this issue. We no longer use the immutables generated class, though we do use the builder. Instead, we use our own implementation, which hashes consistently and uses two map implementations to ensure that in the cases we see, equality, hash, and extending with another tag are cheap.
Generate changelog in
|
Would you mind adding a representative JMH benchmark? |
That seems like a great idea. |
Chatted to @j-baker on slack, would be good to see a profile on this (possibly in prod) before we merge this as it's used by every product. |
Could we do something with a TaggedMetricRegistry.forEach(BiConsumer<MetricName, Metric>) instead? |
I ended up doing both (and came to the forEach conclusion as well). Here are benchmark results before and after:
as you can see, even in the case where we only have a single pre-existing tag, this is a considerable improvement. |
This is also only to write 5k metrics - in practice we see many more. |
I think we should update the benchmark into a root TaggedMetricRegistry with N tagged metrics, a child wrapped DropwizardMetricSet(MetricRegistry) with N metrics, and a child TaggedMetricRegistry with N metrics (representing SharedTaggedMetricRegistries.getSingleton()). I'd prefer to put all our eggs in the forEachMetric basket and avoid optimizations on getMetrics to reduce complexity. If we provide a simple MetricName implementation which doesn't pre-compute hashCode, and avoid precomputing hashcode on ExtraEntrySortedMap, we should have strictly less work. Might be worth special casing adding a single tag to a MetricName with no tags, and adding a simple MetricName implementation around a safeName with no tags for DropwizardTaggedMetricSet. |
import net.jqwik.api.constraints.IntRange; | ||
import net.jqwik.api.constraints.Size; | ||
|
||
class ExtraEntrySortedMapTest { |
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've added this property based test as I wanted to make sure the behaviour of ExtraEntrySortedMap
was correct but also not write a ton of example based tests. This checks that every method call on ExtraEntrySortedMap
produces the same result as the same method call on an equivalent guava ImmutableSortedMap
, for thousands of arbitrary base maps and extra values. It uses jqwik.
private final SortedMap<K, V> delegate; | ||
private final int hashCode; | ||
|
||
PrehashedSortedMap(SortedMap<K, V> delegate) { |
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.
would suggest making immutability explicit in types
PrehashedSortedMap(SortedMap<K, V> delegate) { | |
PrehashedSortedMap(ImmutableSortedMap<K, V> delegate) { |
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 would require casting in RealMetricName, which I'd like to avoid.
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.
not sure I follow that as the only caller of this constructor is com.palantir.tritium.metrics.registry.RealMetricName#prehash
which explicitly passes in an ImmutableSortedMap
return new PrehashedSortedMap<>(ImmutableSortedMap.copyOfSorted(map));
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.
oops!
tritium-registry/src/main/java/com/palantir/tritium/metrics/registry/PrehashedSortedMap.java
Outdated
Show resolved
Hide resolved
tritium-registry/src/main/java/com/palantir/tritium/metrics/registry/ExtraEntrySortedMap.java
Outdated
Show resolved
Hide resolved
tritium-registry/src/main/java/com/palantir/tritium/metrics/registry/ExtraEntrySortedMap.java
Outdated
Show resolved
Hide resolved
tritium-registry/src/main/java/com/palantir/tritium/metrics/registry/ExtraEntrySortedMap.java
Outdated
Show resolved
Hide resolved
tritium-registry/src/main/java/com/palantir/tritium/metrics/registry/ExtraEntrySortedMap.java
Show resolved
Hide resolved
tritium-registry/src/main/java/com/palantir/tritium/metrics/registry/ExtraEntrySortedMap.java
Show resolved
Hide resolved
tritium-registry/src/main/java/com/palantir/tritium/metrics/registry/ExtraEntrySortedMap.java
Show resolved
Hide resolved
tritium-registry/src/main/java/com/palantir/tritium/metrics/registry/PrehashedSortedMap.java
Outdated
Show resolved
Hide resolved
private int computeHashCode() { | ||
int hash = 5381; | ||
hash += (hash << 5) + safeName().hashCode(); | ||
hash += (hash << 5) + safeTags().hashCode(); |
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 curious why djb2 hash algorithm?
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 the one in the immutables generated code
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.
copy-pasted :P
tritium-registry/src/main/java/com/palantir/tritium/metrics/registry/ExtraEntrySortedMap.java
Outdated
Show resolved
Hide resolved
@@ -469,7 +469,7 @@ | |||
<module name="NestedTryDepth"/> <!-- Java Coding Guide: Try/catch blocks: never nested --> | |||
<module name="NonEmptyAtclauseDescription"/> <!-- Java Style Guide: At-clauses --> | |||
<module name="ParameterName"> <!-- Java Style Guide: Parameter names --> | |||
<property name="format" value="^[a-z][a-zA-Z0-9]+$"/> | |||
<property name="format" value="^_?[a-z][a-zA-Z0-9]+$"/> |
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.
as updated by baseline
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.
assume we will also pick this up next time the robots do their rounds in https://github.com/palantir/gradle-baseline/releases/tag/2.7.0 though I don't see a tritium excavator listed in palantir/gradle-baseline#833
1c39f80
to
90456c8
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.
LGTM 👍 , thanks James! Defer to you all when you want to merge & cut a release
@@ -469,7 +469,7 @@ | |||
<module name="NestedTryDepth"/> <!-- Java Coding Guide: Try/catch blocks: never nested --> | |||
<module name="NonEmptyAtclauseDescription"/> <!-- Java Style Guide: At-clauses --> | |||
<module name="ParameterName"> <!-- Java Style Guide: Parameter names --> | |||
<property name="format" value="^[a-z][a-zA-Z0-9]+$"/> | |||
<property name="format" value="^_?[a-z][a-zA-Z0-9]+$"/> |
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.
assume we will also pick this up next time the robots do their rounds in https://github.com/palantir/gradle-baseline/releases/tag/2.7.0 though I don't see a tritium excavator listed in palantir/gradle-baseline#833
Released 0.15.6 |
This might seem like an unnecessary optimization, but profiling
internally implies this uses plenty of CPU and allocates gigabytes and
gigabytes and gigabytes (about 1.8GB of allocations to report metrics).
BLUF: With nested tags, our tooling spends all of its time recreating
nested sorted maps and rehashing, which is very expensive. That said,
breaking binary backcompat here is super expensive, as we know from past
experience.
This PR solves this issue. We no longer use the immutables generated
class, though we do use the builder. Instead, we use our own
implementation, which hashes consistently and uses two map
implementations to ensure that in the cases we see, equality, hash, and
extending with another tag are cheap.