-
Notifications
You must be signed in to change notification settings - Fork 3.7k
[improve][broker] Improve the performance of TopicName #24463
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: master
Are you sure you want to change the base?
[improve][broker] Improve the performance of TopicName #24463
Conversation
Before 48ffb7a, there is a constructor parameter that determines whether to initialize the The benchmark result is:
As you can see
|
I wouldn't trust JMH results from benchmarking on Mac OS. In the case of PR #24457, the results are very different when benchmarking on Linux x86_64 with Intel i9 processor (Dell XPS 2019 laptop). |
@lhotari So could you help run benchmark in your Linux environment to see the difference? I can also create a workflow via GitHub Actions to see the result. |
I don't want to debate about it in this PR. If you have a chance to look at how The ultimate solution is to create some utils methods instead of leveraging the |
Updated test results to compare it with the legacy implementation in https://github.com/BewareMyPower/pulsar/actions/runs/15872434081/job/44752001433?pr=45
|
48ffb7a
to
355140a
Compare
@lhotari @codelipenghui @nodece @dao-jun @poorbarcode @coderzc This PR is now ready to review, PTAL |
@Override | ||
public NamespaceName getNamespaceObject() { | ||
if (namespaceName != null) { | ||
return namespaceName; |
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.
We shouldn't need to have namespaceName
volatile, we can just check it again in the synchronized block.
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.
But could it have the issue with double-checked locking?
The code generated by the compiler is allowed to update the shared variable to point to a partially constructed object before A has finished performing the initialization.
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.
But could it have the issue with double-checked locking?
That's true, there's a good explanation of the problem in https://shipilev.net/blog/2014/safe-public-construction/
The double-checked locking pattern isn't recommended in general since it could lead to a situation where the shared instance seems to be partially instantiated, unless it's completely immutable (this applies to possibly created contained instances in the constructor). In Java "benign data races" don't lead to crashes, but to potential consistency issues where the state of fields are inconsistent.
Perhaps in this case, double checked locking would be possible assuming that NamespaceName is completely immutable in regards to fields set in the constructor?
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.
Perhaps in this case, double checked locking would be possible assuming that NamespaceName is completely immutable in regards to fields set in the constructor?
According to https://shipilev.net/blog/2014/safe-public-construction/, it wouldn't be safe. There isn't really a way around 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.
Safe publication makes all the values written before the publication visible to all readers that observed the published object. It is a great simplification over the JMM rules of engagement with regards to actions, orders and such.
There are a few trivial ways to achieve safe publication:
Exchange the reference through a properly locked field (JLS 17.4.5)
Use static initializer to do the initializing stores (JLS 12.4)
Exchange the reference via a volatile field (JLS 17.4.5), or as the consequence of this rule, via the AtomicX classes
Initialize the value into a final field (JLS 17.5).
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.
Well in this case, since we aren't constructing NamespaceName but instead calling NamespaceName.get (which handles thread safety with the cache implementation), it would be possible to avoid using a volatile field and just use DCL.
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.
But the thread safety depends on the implementation of NamespaceName.get
. It would also be dangerous in case someone else did copy-and-paste in future.
I decided to remove the lazy initialization for now. The prerequisite of the performance improvement is that the TopicName
's constructor is called more frequently than the getNamespace
or getNamespaceObject
methods.
Let's use a conservative solution for now
pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java
Outdated
Show resolved
Hide resolved
microbench/src/main/java/org/apache/pulsar/broker/qos/TopicNameBenchmark.java
Outdated
Show resolved
Hide resolved
@BewareMyPower My point from an earlier comment isn't addressed:
In this case, I think it's irrelevant to just benchmark the performance of creating/looking up TopicName instances. Duplicate I do agree that a lot of the TopicName handling code is a mess. For example in Topic listing, the topic name is converted from/to String multiple times. That's adding up a lot of pressure for a fast lookup solution when instances are cached. So I'm not against changing the current caching, it's just that there's a need to consider duplicate instances as well. It's likely that the caching is more relevant for NamespaceName than TopicName regarding duplicate instances. We might not be keeping a reference to TopicName instance in that many places when broker is running. |
I've read more code and changed my idea a bit. Caching could be helpful in many cases. But how to establish the cache might depend on specific use cases. Writing a common cache is hard. I don't like the solution in #23052, but it's anyway good to resolve the issue encountered at that time. As I've mentioned here, exposing the public method is helpful for downstream to construct its own cache. In addition, the
It's just an example, |
Regarding this PR, I'm going to revert other changes and only leaving the improvement on Exposing a public method will be easier for the downstream to maintain its custom cache, but it will also be confusing for the core Pulsar developers to make the decision on Anyway, we should improve the use of
The In this case, |
This PR will be an improvement. Regarding the argument I made in a previous comment about duplicate tenant and namespace |
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
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.
For deduplicating the tenant and namespace String instances, it would be useful to assign the tenant
and namespacePortion
fields from the NamespaceName
instance. This wouldn't add much overhead, but benefit in reducing the amount of heap memory since there would be less duplication of java.lang.String
instances at runtime.
Motivation
The
TopicName
's constructor has poor performance:NamespaceName#get
is very slowSplitter.on("/").splitToList(rest)
is slowString.format
is slower than the+
operation on strings andcompleteTopicName
is unnecessarily created againModifications
Initialize(don't do that because it assumes the constructor is called more frequently thanNamespaceName
in a lazy waygetNamespace
orgetNamespaceObject
)Splitter
with a manually writtensplitBySlash
introduced from [fix][proxy] Fix proxy OOM by replacing TopicName with a simple conversion method #24465. Actually,StringUtils#split
has good performance as well. But it will split"//xxx/yyy/zzz"
to["xxx", "yyy", "zzz"]
without reporting any error.completeTopicName
from the argument directly without any concentrate operationString.format
with+
infromPersistenceNamingEncoding
Documentation
doc
doc-required
doc-not-needed
doc-complete
Matching PR in forked repository
PR in forked repository: BewareMyPower#44