-
Notifications
You must be signed in to change notification settings - Fork 336
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
Optimize the performance by passing MessageID implementations by pointers #968
Optimize the performance by passing MessageID implementations by pointers #968
Conversation
6cff537
to
6fc2fe9
Compare
6fc2fe9
to
75ca0ae
Compare
75ca0ae
to
00bec29
Compare
a83e350
to
f7d69df
Compare
Wait for #970 to be merged first. |
### Motivation Currently there are three implementations of the `MessageID` interface: - `messageID`: 24 bytes - `trackingMessageID`: 64 bytes - `chunkMessageID`: 80 bytes However, for all methods of them, the receiver is a value rather than a pointer. It's inefficient because each time a method is called, the copy would happen. Reference: https://go.dev/tour/methods/8 ### Modifications - Change the receiver from value to pointer for all `MessageID` implementations. - Use pointers as the returned values and function parameters for these implementations everywhere. The `trackingMessageID.Undefined` method is removed because it's never used now. Though it's a public method, the struct and its factory function are not exposed, so I think it's reasonable. Remove the benchmark added in apache#324. The result is obvious and this test is meaningless. I tried passing the `trackingMessageID` by pointer and the result reduced from 8.548 ns/op to 1.628 ns/op. It's obvious because a pointer is only 8 bytes while a `trackingMessageID` is 64 bytes. The overhead of accessing by pointers is far less than copying the extra bytes.
1c32ee4
to
e29533b
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.
Generally looks good.
Do you have a bench result on this change? Since you declaim that it's a performance improvement.
Also, why do you remove impl_message_bench_test.go
?
This test has no assertions and should not be in unit tests. |
I think they're go benches which will be only triggered if you run |
I will paste a result soon. Though it might not be significant.
See my PR description.
|
Test processI created a branch from master and apply the following patch to disable ACK grouping (so that each MessageID could be passed) diff --git a/perf/perf-consumer.go b/perf/perf-consumer.go
index 6b6e411..c7648e3 100644
--- a/perf/perf-consumer.go
+++ b/perf/perf-consumer.go
@@ -79,6 +79,7 @@ func consume(consumeArgs *ConsumeArgs, stop <-chan struct{}) {
Topic: consumeArgs.Topic,
SubscriptionName: consumeArgs.SubscriptionName,
EnableBatchIndexAcknowledgment: consumeArgs.EnableBatchIndexAck,
+ AckGroupingOptions: &pulsar.AckGroupingOptions{MaxSize: 0},
})
if err != nil { Then, build the perf tool. cd perf
go build Since this patch mainly affects the MessageID related operations, let's only test the consumer with each message acknowledged. Start Pulsar 2.11 standalone. Create two subscriptions ./perf consume --enable-batch-index-ack my-topic -s sub1
./perf consume --enable-batch-index-ack my-topic -s sub2 Then, produce some messages and wait for a while and stop the producer. ./perf produce --batching-num-messages=1 --rate=200000 my-topic Then we can test the catch-up read. First, test without this patch: go build
./perf consume --enable-batch-index-ack my-topic -s sub1 Then, apply this patch and repeat the steps above:
Before running the perf tool each time, restart the standalone. Test resultWithout this patch:
With this patch:
After this patch, the throughput is more stable and is a little higher than before. |
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 for your report.
Although I think the performance is not quite optimized, checking mid, ok
looks weird to me.
I'm +1 as it simplifies code without perf regression.
### Motivation `toTrackingMessageID()` is a function that is widely used in `consumer` implementation. It can convert an interface type `MessageID` into an struct type `trackingMessageID`. In addition, the second return value also plays a role in checking the `MessageID` type. In other words, it indicates that `MessageID` **cannot** be a user-defined type. From the perspective of code readability, `toTrackingMessageID()` should not do both. **Note**: After #968 , `toTrackingMessageID()` returns only a pointer now. The role of original `ok` is replaced by nil pointer now. However, the main content discussed in this PR has not changed. For example. https://github.com/apache/pulsar-client-go/blob/e2ea255052e8a527091791ef368851d885ee2d45/pulsar/consumer_regex.go#L176-L181 This example is the correct usage. The `ok` returned by `toTrackingMessageID()` is used to reject user-defined `MessageID`. https://github.com/apache/pulsar-client-go/blob/e2ea255052e8a527091791ef368851d885ee2d45/pulsar/consumer_partition.go#L470-L473 This example is a bit vague. The actual effect here is the same as the previous example. But it return an error `failed to convert trackingMessageID` which is confusing. https://github.com/apache/pulsar-client-go/blob/e2ea255052e8a527091791ef368851d885ee2d45/pulsar/consumer_partition.go#L1816-L1820 In this case. We just want to convert `MessageID` into `trackingMessageID`. We do not care what it really is because it's not possible an invalid `MessageID` implementation. So, original `toTrackingMessageID()` needs to require a careful look to use it correctly. I think it would be better to split it into two different method. `toTrackingMessageID()` just do the struct conversion, which it's more clearly. And when the new messageID type is added, we can just modify the `checkMessageIDType`. ### Modifications - Refactor the `toTrackingMessageID()` - Add the `checkMessageIDType()` to check whether `MessageID` is user-defined.
Motivation
Currently there are three implementations of the
MessageID
interface:messageID
: 24 bytestrackingMessageID
: 64 byteschunkMessageID
: 80 bytesHowever, for all methods of them, the receiver is a value rather than a pointer. It's inefficient because each time a method is called, the copy would happen.
Reference: https://go.dev/tour/methods/8
Modifications
MessageID
implementations.The
trackingMessageID.Undefined
method is removed because it's never used now. Though it's a public method, the struct and its factory function are not exposed, so I think it's reasonable.Remove the benchmark added in
#324. The result is obvious and this test is meaningless. I tried passing the
trackingMessageID
by pointer and the result reduced from 8.548 ns/op to 1.628 ns/op. It's obvious because a pointer is only 8 bytes while atrackingMessageID
is 64 bytes. The overhead of accessing by pointers is far less than copying the extra bytes.