-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Elide repeated Action-delegate allocation by caching it #1324
Conversation
Here C# compiler has too little knowledge about the usage of the delegate, we know better so cache it on our own to avoid the repeated allocation.
@@ -150,7 +156,7 @@ public async Task<int> ReadAsync(byte[] buffer, int offset, int count, Cancellat | |||
} | |||
|
|||
// Workaround for: https://github.com/dotnet/corefx/issues/24430 |
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.
Question: starting with .NET Core 3.0 this workaround isn't needed anymore, so the code could be conditionally compiled for a "legacy" target and for new targets.
W/o the workaround perf in general should be better, as the cost of creating, and disposing the cancellationtoken-registration can be avoided.
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.
If it is fixed we should add a compiler switch (NETCOREAPP3_0_OR_GREATER).
@@ -150,7 +156,7 @@ public async Task<int> ReadAsync(byte[] buffer, int offset, int count, Cancellat | |||
} | |||
|
|||
// Workaround for: https://github.com/dotnet/corefx/issues/24430 | |||
using (cancellationToken.Register(Dispose)) |
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.
In the above code I agree that the lambda is not required but here we already have a method without wrapping it. Do we really need that optimization here as well?
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.
From my profiling it showed up in WriteAsync
(L187), so it's just duplicated here.
But when the conditional compilation is added for .NET Core 3.0 and newer, this won't be hit. So for the legacy code we could
- keep the state of master-branch
- keep the cached delegate of PR's current state
I'm leaning to the latter, as the change isn't huge and so older targets can benefit from that change 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.
Ah, now I see your concern. Yes we need it. Whether it's a delegate created from a method group or a lambda, the delegate won't be cached. Checked the IL for both.
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.
OK then we should keep it.
@@ -112,7 +118,7 @@ public async Task ConnectAsync(string host, int port, CancellationToken cancella | |||
_networkStream?.Dispose(); | |||
|
|||
// Workaround for: https://github.com/dotnet/corefx/issues/24430 | |||
using (cancellationToken.Register(() => _socket.Dispose())) |
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.
Is the bug also fixed in this case so adding the pragma here works as well?
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.
Socket.ConnectAsync(string, int, CancellationToken)
exists only starting from .NET 6.
So for the moment we have to stay at the current 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.
OK.
Thank you for your contribution. Good work 👍 |
commit 27592f1 Author: Christian <[email protected]> Date: Sat Jan 8 15:36:35 2022 +0100 Update MQTTnet.nuspec commit c6f4b59 Author: Christian <[email protected]> Date: Sat Jan 8 15:35:47 2022 +0100 Increase delay for Keep Alive checks to decrease CPU load. commit b362b2a Author: Christian <[email protected]> Date: Sat Jan 8 14:35:25 2022 +0100 Make logger parameters generic. commit 7a72a8d Author: Christian <[email protected]> Date: Sat Jan 8 14:07:57 2022 +0100 Update MQTTnet.nuspec commit bd40e10 Author: Günther Foidl <[email protected]> Date: Sat Jan 8 14:00:39 2022 +0100 Elide repeated Action-delegate allocation by caching it (#1324) * Elide repeated Action-delegate allocation by caching it Here C# compiler has too little knowledge about the usage of the delegate, we know better so cache it on our own to avoid the repeated allocation. * Use conditional compilation commit 4b44a2b Author: Christian <[email protected]> Date: Fri Jan 7 18:01:03 2022 +0100 Remove appveyor. commit 215f24a Author: Christian <[email protected]> Date: Fri Jan 7 17:52:16 2022 +0100 Create CODE-OF-CONDUCT.md
@gfoidl static readonly Action<object> _socketDisposeAction = state => ((Socket)state).Dispose();
using (cancellationToken.Register(_socketDisposeAction, _socket)) |
@yyjdelete that's another way to do it, but I don't know if it is actually "better". So for the use here it could be done either way. In this PR I just the most easy to write and read way. |
Here C# compiler has too little knowledge about the usage of the delegate, we know better so cache it on our own to avoid the repeated allocation.
From the profiling session in #1323 we see that there are ~100.000 allocations of the
Action
-delegate. They come fromcancellationToken.Register
and the C# compiler can't cache the method-group here.With the change from this PR these allocations are gone. The following images shows the changes of #1323 combined with this one:
So for the client publishing use case only the allocations for tasks, the publish packet, and the publish result show up.
To avoid these allocations I fear some bigger refactorings are needed, and thats a different story...