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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge SocketAsyncEventArgs into SocketAwaitable and use cached instances #18

Merged
merged 4 commits into from
Jun 18, 2023

Conversation

gfoidl
Copy link
Contributor

@gfoidl gfoidl commented Jun 16, 2023

For socket operation:
Instead of creating (allocating) a SocketAsyncEventArgs and then a SocketAwaitable both are "merged" into one instance, which can be cached and re-used. Thus avoiding allocations and make it amortized allocation-free.

This could be driven further, by using ValueTask instead of Task. But with the current targets (< .NET 6) it's a bit cumbersome, so I didn't do this now.


@mycroes you mentioned this project in Sally7, so I had a quick look 馃槈.

Copy link
Contributor Author

@gfoidl gfoidl left a comment

Choose a reason for hiding this comment

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

Some notes for review.

Comment on lines +16 to +17
private readonly SocketAwaitable socketAwaitable = new SocketAwaitable();
private readonly byte[] headerBuffer = new byte[AmsHeaderHelper.AmsTcpHeaderSize];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As the ReceiveLoop handles one receive after another, so in a serialized manner, the awaitable and the buffer can be re-used.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, I only got to optimizing the sending side of things.

}

private bool closed;
private volatile bool closed;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

To be on the safe side, this should be volatile as in the receive-loop it's checked (read) and at that point different threads are in play.

For x86-machines it's unlikely to be a real problem, but for good code hygiene and for platforms with a weak memory model it's a good addition. And no harm to x86-machines.

}

private async Task ReceiveLoop()
{
await Task.Yield();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Instead of the Task.Run in the ctor of this class.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Task.Run was borrowed from RabbitMQ.Client, but I like this one as well.

@@ -6,7 +6,7 @@ internal class Assertions
{
public static void AssertDataLength(ReadOnlySpan<byte> buffer, int length, int offset)
{
if (length + offset != buffer.Length)
if (length != buffer.Length - offset)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm paranoid about overflow, so prevent that where possible...

@@ -10,18 +10,18 @@ namespace Viscon.Communication.Ads.Internal;
/// </remarks>
internal static class SocketExtensions
{
public static SocketAwaitable ReceiveAsync(this Socket socket, SocketAwaitable awaitable)
public static SocketAwaitable ReceiveAwaitable(this Socket socket, SocketAwaitable awaitable)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needed to change the name, otherwise the compiler won't pick that extension method, as an overload on Socket itself would be choosen.

@mycroes
Copy link
Collaborator

mycroes commented Jun 16, 2023

Thanks a lot for these changes, I'll take another look at them soon and will merge them then.

Copy link
Collaborator

@mycroes mycroes left a comment

Choose a reason for hiding this comment

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

One minor question, other than that it's great to again see the improvements you found.

@@ -28,6 +28,8 @@ internal static class TypeExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref byte GetOffset(this ref byte source, int offset)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't offset be uint here as well to just avoid the cast at line 33 altogether?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's possible too, but then there's the cast in L25. If the parameter of that method is changed to uint too, then the cast will likely be somewhere else.

So I think it's just fine to have it here (once), and not scattered around the code-base.

@mycroes mycroes merged commit 16abe22 into VisconFactoryIntelligence:main Jun 18, 2023
3 checks passed
@mycroes
Copy link
Collaborator

mycroes commented Jun 18, 2023

Thanks a lot G眉nther!

@gfoidl gfoidl deleted the allocation-tweaks branch June 19, 2023 08:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants