-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
[Azure Service Bus] Remove unnecessary LINQ on AmqpReceiver #11272
Conversation
Can one of the admins verify this patch? |
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs
Outdated
Show resolved
Hide resolved
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs
Outdated
Show resolved
Hide resolved
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs
Outdated
Show resolved
Hide resolved
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs
Outdated
Show resolved
Hide resolved
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs
Outdated
Show resolved
Hide resolved
Is there a benchmark/profile showing the improvement? |
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs
Outdated
Show resolved
Hide resolved
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs
Outdated
Show resolved
Hide resolved
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs
Outdated
Show resolved
Hide resolved
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpSender.cs
Outdated
Show resolved
Hide resolved
@pakrym happy to provide that if you think this is worth pursuing. I'll revert first the other changes @ShivangiReja pointed out |
It's always good to have an idea of before/after and how the improvement compares to overall performance. |
6c3a6ba
to
6383d1e
Compare
@pakrym added the benchmark results with the changed code that hopefully also addresses the other comments. I ended up with a combination of LINQ and foreach to avoid the Any which leads to very good results. I'm executing a medium run as I post this and will update the post as soon as I have the results I'll leave it up to you if you want to take this or not. I have no problems closing this. |
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.
Changes look good to me! Thank you @danielmarbach!
@pakrym Could you please take a look?
Guid[] lockTokenGuids = lockTokens.Select(token => new Guid(token)).ToArray(); | ||
if (lockTokenGuids.Any(lockToken => _requestResponseLockedMessages.Contains(lockToken))) | ||
bool requestResponse = false; | ||
var lockTokenGuids = lockTokens.Select(token => new Guid(token)).ToArray(); |
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.
Why not get rid of this piece of linq too?
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 did an attempt to do it by creating an array of guid. I then needed to ask for the count on the enumerable which lead to less allocations as the original version but worse performance
The best gain in the test results above is for the array of 4 elements and it is 175 ns. How many times this method is called? |
/azp run net - servicebus - tests |
Azure Pipelines successfully started running 1 pipeline(s). |
@danielmarbach thanks for these performance fixes! They are greatly appreciated 😄 Out of curiosity, have you profiled any end to end scenarios like receiving or sending to identify any bottlenecks? If so, we would be interested to learn about your findings. |
/azp run net - servicebus - tests |
Azure Pipelines successfully started running 1 pipeline(s). |
I'm sorry for being stubborn, but I'm still interested in the scenario we're optimizing. I've done a simple benchmark based on the danielmarbach/MicroBenchmarks@7a474da#diff-5d50ca96fc4df16892b002db1d70e8c3: private static async Task<Guid[]> LinqWithAwait(IEnumerable<string> lockTokens)
{
var lockTokenGuids = lockTokens.Select(token => new Guid(token)).ToArray();
if (lockTokenGuids.Any(lockToken => _requestResponseLockedMessages.Contains(lockToken)))
return await Task.FromResult(lockTokenGuids).ConfigureAwait(false);
return await Task.FromResult(Array.Empty<Guid>()).ConfigureAwait(false);
}
private static async Task<Guid[]> ForeachWithAwait(IEnumerable<string> lockTokens)
{
var requestResponse = false;
var lockTokenGuids = lockTokens.Select(token => new Guid(token)).ToArray();
foreach (var tokenGuid in lockTokenGuids)
{
if (!requestResponse && _requestResponseLockedMessages.Contains(tokenGuid)) requestResponse = true;
}
if (requestResponse)
{
return await Task.FromResult(lockTokenGuids).ConfigureAwait(false);
}
return await Task.FromResult(Array.Empty<Guid>()).ConfigureAwait(false);
}
private static Task<Guid[]> LinqWithoutAwait(IEnumerable<string> lockTokens)
{
var lockTokenGuids = lockTokens.Select(token => new Guid(token)).ToArray();
if (lockTokenGuids.Any(lockToken => _requestResponseLockedMessages.Contains(lockToken)))
return Task.FromResult(lockTokenGuids);
return Task.FromResult(Array.Empty<Guid>());
} Here are the results:
As you can see, simple removal of |
6383d1e
to
a64e875
Compare
@pakrym I added the break version, new results BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.201
[Host] : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT
MediumRun : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT
Job=MediumRun IterationCount=15 LaunchCount=2
WarmupCount=10
|
@AlexanderSher It is true that in comparison to an IO-bound method this could be seen as unnecessary because one could argue the linq version could have better readability. What I have seen though for example with rabbitmq when fetching/sending messages under load those bytes can count. @JoshLove-msft I rebased to get your other fixes. For other PRs is there a way for me to have a full run done as part of the PR? It seems my previous PR was green but did cause problems when the pipeline run on master. |
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs
Outdated
Show resolved
Hide resolved
Unfortunately, this would only be caught by the live tests which won't run automatically. I think it is on us at Microsoft to make sure we run the live tests before merging. |
/azp run net - servicebus - tests |
Azure Pipelines successfully started running 1 pipeline(s). |
It looks like all the comments have been settled. I'm planning on merging this in assuming the live tests pass. |
@danielmarbach looks like there is a conflict now with something that I merged in. Sorry about that. Would you mind resolving this? |
d3fb0e9
to
ca2fdac
Compare
Rebased |
/azp run net - servicebus - tests |
Azure Pipelines successfully started running 1 pipeline(s). |
Removes LINQ usage from the AmqpReceiver from the hot path to improve speed and allocations
I tried to document reasoning inline
Results
Benchmark used https://github.com/danielmarbach/MicroBenchmarks/blob/438dac249a7a44e178b758e860cf16ca834b0c79/MicroBenchmarks/Linq/LinqGuidConversionContains.cs