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

Introduce CachedToStringMetricKey and CachedToStringMetricName for Improved Performance #127

Merged

Conversation

FuRyanf
Copy link

@FuRyanf FuRyanf commented Sep 13, 2024

PR Description:

Introduce CachedToStringMetricKey and CachedToStringMetricName for Improved Performance

Summary:

This PR introduces two new classes, CachedToStringMetricKey and CachedToStringMetricName, which cache the result of the toString() method to improve performance in scenarios where toString() is frequently called.

Key Changes:

  • CachedToStringMetricKey: A wrapper around MetricKey that caches the toString() result upon construction, reducing overhead from repeated toString() calls in performance-sensitive areas.
  • CachedToStringMetricName: Similar to CachedToStringMetricKey, this class wraps around MetricName and caches its toString() value for efficiency, while still delegating all other methods to the original MetricName.
  • equals() and hashCode() Overrides: To ensure consistency in collections, equals() and hashCode() were overridden to delegate these comparisons to the underlying MetricName and MetricKey instances. This ensures correct behavior when these objects are used as keys in maps.
  • Handling @Nullable Parameters: Updated the equals() method to handle @Nullable parameters correctly, following Java’s contract for equals() and ensuring compatibility with null comparisons.
  • Replaced @Memoized in MetricKey and MetricName with the new cached classes, removing the overhead caused by the @Memoized pattern.

Motivation:

The use of @Memoized for the toString() method in the auto-generated MetricKey and MetricName classes introduces unnecessary overhead due to its thread-safe memoization pattern, which includes additional synchronization logic. This pattern creates a transient volatile String toString field and performs a synchronized check with every toString() call. This can degrade performance, especially when toString() is frequently invoked in performance-sensitive parts of the metrics system.

By caching the toString() value at construction, we eliminate this overhead, leading to significant performance improvements. Benchmarks have shown that even directly computing toString() is faster than the memoized implementation. The new approach optimizes for frequent toString() invocations without the cost of synchronization.

Changes:

  • Removed @Memoized annotations from MetricKey and MetricName.
  • Updated MetricKey.create() to use CachedToStringMetricKey and MetricName construction to use CachedToStringMetricName.
  • Added new classes to handle caching logic for toString() and ensured consistent usage across the codebase to avoid potential issues with mixed types in collections.

Thank you for your contribution! Follow this checklist to help us incorporate your contribution quickly and easily:

  • Mention the appropriate issue in your description (for example: addresses #123), if applicable. This will automatically add a link to the pull request in the issue. If you would like the issue to automatically close on merging the pull request, comment fixes #<ISSUE NUMBER> instead.
  • Update CHANGES.md with noteworthy changes.
  • If this contribution is large, please file an Apache Individual Contributor License Agreement.

See the Contributor Guide for more tips on how to make review process smoother.

To check the build health, please visit https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md

GitHub Actions Tests Status (on master branch)

Build python source distribution and wheels
Python tests
Java tests
Go tests

See CI.md for more information about GitHub Actions CI.

@github-actions github-actions bot added the java label Sep 13, 2024
@FuRyanf FuRyanf force-pushed the FuRyanf/optimize-metric-key-name-cache branch from d6088f0 to 334ce0e Compare September 13, 2024 16:50
@github-actions github-actions bot added the build label Sep 13, 2024
@FuRyanf FuRyanf force-pushed the FuRyanf/optimize-metric-key-name-cache branch 4 times, most recently from 4ed7daa to 429075a Compare September 13, 2024 19:01
@FuRyanf FuRyanf force-pushed the FuRyanf/optimize-metric-key-name-cache branch from 429075a to a64ed43 Compare September 13, 2024 19:12
public String toString() {
return stepName() + ":" + metricName();
}

public static MetricKey create(@Nullable String stepName, MetricName metricName) {
return new AutoValue_MetricKey(stepName, metricName);
return new CachedToStringMetricKey(new AutoValue_MetricKey(stepName, metricName));
Copy link
Collaborator

Choose a reason for hiding this comment

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

ha, I thought that we will create a CachedToStringMetricKey , that replaces the AutoValue_MetricKey instead of wrapping the AutoValue_MetricKey. But either way should be fine.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not a fan of autovalue. :)

Copy link
Author

Choose a reason for hiding this comment

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

Haha, I see what you mean! Initially, I also thought about directly creating a CachedToStringMetricKey to replace AutoValue_MetricKey, but I realized that AutoValue provides its own implementations of .equals and .hashcode, which I wanted to preserve. That’s why it evolved into the wrapper approach—my goal was to minimize changes while keeping the core behavior intact.

public String toString() {
return getNamespace() + ":" + getName();
}

public static MetricName named(String namespace, String name) {
checkArgument(!Strings.isNullOrEmpty(namespace), "Metric namespace must be non-empty");
checkArgument(!Strings.isNullOrEmpty(name), "Metric name must be non-empty");
return new AutoValue_MetricName(namespace, name);
return new CachedToStringMetricName(new AutoValue_MetricName(namespace, name));
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

}

public static MetricName named(Class<?> namespace, String name) {
checkArgument(namespace != null, "Metric namespace must be non-null");
checkArgument(!Strings.isNullOrEmpty(name), "Metric name must be non-empty");
return new AutoValue_MetricName(namespace.getName(), name);
return new CachedToStringMetricName(new AutoValue_MetricName(namespace.getName(), name));
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

@FuRyanf FuRyanf merged commit af5a917 into linkedin:li_trunk Sep 14, 2024
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants