1

SocketAsyncEventArgs is used to past the buffer, offset and length into the ReceiveAsync and SendAsync method of Socket.

I had done tests which revealed that,

  • If I don't dispose SocketAsyncEventArgs, it will cause memory leak.
  • Creating it, disposing it and calling its SetBuffer method all consume a huge amount of CPU.

It seems like a very bad and buggy implementation that SocketAsyncEventArgs, which simply acts as a wrapper to pass a buffer, its offset and length into the async methods of Socket, would consume so much CPU.

Does anyone know why? How do I use it with the async methods of Socket, then, without significantly slowing down my high-throughput server?

Following is the testing code. This Test method is called by 1000 threads. It is called 1000 times per second. But if I remove SocketAsyncEventArgs, it is called 8000 times per second. This seems to indicate that SocketAsyncEventArgs.SetBuffer consumes a huge amount of CPU.

    private static void Test(object obj)
    {
        TrueRandom random = new TrueRandom(500, 1300);
        SocketAsyncEventArgs args = new SocketAsyncEventArgs();

        while (true)
        {
            int iDesiredSize = random.GetRandomInteger() * 1000;
            byte[] buffer = null;

            try
            {
                buffer = ArrayPool<byte>.Shared.Rent(iDesiredSize);
                args.SetBuffer(buffer, 0, buffer.Length);
                ArrayPool<byte>.Shared.Return(buffer);
            }
            catch
            {
            }

            Thread.Sleep(100);
        }
    }
  • 1
    There is *some* overhead with swapping a buffer, as it needs to pin the array etc; have you considered keeping the same buffer for multiple socket operations? Or just using something like "pipelines" which deals with all this for you? – Marc Gravell Nov 07 '21 at 12:25
  • 1
    Also: how large are the buffers you're creating here? `random.GetRandomInteger() * 1000` sounds like it could be pretty big; note that a: by being so randomly sized, you're likely getting a lot of misses here and having to create new arrays (because there hasn't been a suitably sized one pooled); also: above a certain size, it *always* allocates a new array (never pools) – Marc Gravell Nov 07 '21 at 12:26
  • 1
    Honestly, if you're trying to measure SAEA, you should create two buffers only, and time swapping between them in a loop. Otherwise: the thing you're measuring **isn't SAEA** – Marc Gravell Nov 07 '21 at 12:34
  • Thank you so much Mark! Your input has been invaluable for me! FYI, I had three tests: (1) Doing nothing in the whille(true) loop; (2) Only do memory allocation using ArrayPool; (3) Do memory allocation using ArrayPool and SocketAsyncEventArgs.SetBuffer. Test (1) and (2) had no difference - 9000/sec, but test (3) drops to 1000/sec. So the ArrayPool operations had no impact in the test. –  Nov 08 '21 at 00:57
  • I think your suggestion of reusing the SocketAsyncEventArgs object and the buffer inside it is the answer. Just have an ObjectPool of SocketAsyncEventArgs objects, each with a buffer large enough for all TCP packets, and reuse them. If you want to present it as a solution, I will tick it as the solution. –  Nov 08 '21 at 01:00

0 Answers0