Skip to content

Nearly allocation free Mono C# SendTo/ReceiveFrom NonAlloc variants.

License

Notifications You must be signed in to change notification settings

miwarnec/where-allocation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 

Repository files navigation

where-allocation

Nearly allocation free Mono C# SendTo/ReceiveFrom NonAlloc variants.

whereallocation_smaller

Made by vis2k & FakeByte.

ReceiveFrom Allocations

Mono C#'s Socket.ReceiveFrom has heavy allocations (338 byte in Unity):

ReceiveFrom_Before

Which is a huge issue for multiplayer games which try to minimize runtime allocations / GC.

It allocates because IPEndPoint .Create allocates a new IPEndPoint, and Serialize() allocates a new SocketAddress.

Both functions are called in Mono's Socket.ReceiveFrom:

int ReceiveFrom (Memory<byte> buffer, int offset, int size, SocketFlags socketFlags, ref EndPoint remoteEP, out SocketError errorCode)
{
    SocketAddress sockaddr = remoteEP.Serialize();

    int nativeError;
    int cnt;
    unsafe {
        using (var handle = buffer.Slice (offset, size).Pin ()) {
            cnt = ReceiveFrom_internal (m_Handle, (byte*)handle.Pointer, size, socketFlags, ref sockaddr, out nativeError, is_blocking);
        }
    }

    errorCode = (SocketError) nativeError;
    if (errorCode != SocketError.Success) {
        if (errorCode != SocketError.WouldBlock && errorCode != SocketError.InProgress) {
            is_connected = false;
        } else if (errorCode == SocketError.WouldBlock && is_blocking) { // This might happen when ReceiveTimeout is set
            errorCode = SocketError.TimedOut;
        }

        return 0;
    }

    is_connected = true;
    is_bound = true;

    /* If sockaddr is null then we're a connection oriented protocol and should ignore the
     * remoteEP parameter (see MSDN documentation for Socket.ReceiveFrom(...) ) */
    if (sockaddr != null) {
        /* Stupidly, EndPoint.Create() is an instance method */
        remoteEP = remoteEP.Create (sockaddr);
    }

    seed_endpoint = remoteEP;

    return cnt;
}

How where-allocation avoids the Allocations

IPEndPointNonAlloc inherits from IPEndPoint to overwrite Create(), Serialize() and GetHashCode().

  • Create(SocketAddress) does not create a new IPEndPoint anymore. It only stores the SocketAddress.
  • Serialize() does not create a new SocketAddress anymore. It only returns the stored one.
  • GetHashCode() returns the cached SocketAddress GetHashCode() directly without allocations.

Benchmarks

Using Mirror with 1000 monsters, Unity 2019 LTS (Deep Profiling), we previously allocated 8.9 KB:

Mirror - 1k - serveronly - before

With where-allocation, it's reduced to 364 B:

Mirror - 1k - serveronly - after

=> 25x reduction in allocations/GC!

Usage Guide

See the Example folder or kcp2k.

  • Use IPEndPointNonAlloc
  • Use ReceiveFrom_NonAlloc
  • Use SendTo_NonAlloc
  • Use IPEndPointNonAlloc.DeepCopyIPEndPoint() to create an actual copy (once per new connection)

Here is how the server polls, from the Example:

if (serverSocket.Poll(0, SelectMode.SelectRead))
{
    // nonalloc ReceiveFrom
    int msgLength = serverSocket.ReceiveFrom_NonAlloc(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, serverReusableReceiveEP);

    // new connection? then allocate an actual IPEndPoint once to store it.
    if (newClientEP == null)
        newClientEP = serverReusableReceiveEP.DeepCopyIPEndPoint();

    // process the message...
    message = new ArraySegment<byte>(receiveBuffer, 0, msgLength);
}

Tests

where-allocation comes with several unit tests to guarantee stability:

2021-06-01_13-58-31@2x

Showcase

where-allocation is used by:

Remaining Allocations

In Unity 2019/2020, Socket.ReceiveFrom_Internal still allocates 90 bytes because of the oudated Mono version:

2021-11-30_12-17-15@2x

Unity Socket class: https://github.com/Unity-Technologies/mono/blob/unity-2021.2-mbe/mcs/class/System/System.Net.Sockets/Socket.cs

Unity2019 LTS Mono - ReceiveFrom

Unity 2021.2.0.a18 is supposed to have the latest Mono.

Which should automatically get rid of the last allocation.

About

Nearly allocation free Mono C# SendTo/ReceiveFrom NonAlloc variants.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages