C# byte [ ] operations - shall I use ArrayPool or MemoryPool?

1.7k Views Asked by At

I am writing a server application. It accepts thousands of incoming socket connections, each sending and receiving messages.

Every time a message is received or sent over the socket, I used to allocate new byte[] buffers, then they are garbage collected:

byte[] receivingBuffer = new byte[bufferSize];

To improve the performance, I want to reuse the byte[] buffers. Shall I use ArrayPool or MemoryPool?

Or shall I create an ObjectPool of fixed-length byte[] buffers? For example, if the messages I send and receive never exceed 100 KB, then I create an ObjectPool of 100 KB byte[] buffers. Every time I need to send or receive a message, I get one of these buffers.

1

There are 1 best solutions below

0
AudioBubble On

Did some research and testing. ArrayPool is the right thing to use and will dramatically increase performance.

class Program
{
    private static ManualResetEvent m_event = new ManualResetEvent(false);
    private static DateTime m_dtStart;
    private static int m_iNumOfIterations = 0;
    private static int m_iNumOfExceptions = 0;

    private static double m_dAvgSizeRatio = 0;
    private static object m_lock = new object();

    private static void ReportSizeRatio(double dRatio)
    {
        lock (m_lock)
        {
            m_dAvgSizeRatio = (m_dAvgSizeRatio * m_iNumOfIterations + dRatio) / (m_iNumOfIterations + 1);
            m_iNumOfIterations++;
        }
    }

    /// <summary>
    /// When using ArrayPool: 
    /// - CPU:    1 ~ 1.5%
    /// - Memory: 67 MB
    /// - Speed:  9130 runs/sec
    /// - Given buffer is 1.56 times bigger than asked for.
    /// 
    /// When NOT using ArrayPool: 
    /// - CPU:    20 ~ 25%
    /// - Memory: 500 ~ 1000 MB
    /// - Speed:  5300 runs/sec
    /// 
    /// Conclusion: huge improvement in performance.
    /// </summary>
    /// <param name="obj"></param>
    private static void Test(object obj)
    {
        TrueRandom random = new TrueRandom(500, 800);
        m_event.WaitOne();

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

            try
            {
                //buffer = ArrayPool<byte>.Shared.Rent(iDesiredSize);
                buffer = new byte[iDesiredSize];

                ReportSizeRatio((double)buffer.Length / (double)iDesiredSize);
            }
            catch
            {
                Interlocked.Increment(ref m_iNumOfExceptions);
            }

            Thread.Sleep(100);
            //ArrayPool<byte>.Shared.Return(buffer);
        }
    }


    static void Main(string[] args)
    {

        for (int i = 0; i < 1000; i++)
        {
            Thread thread = new Thread(Test);
            thread.Start();
        }

        Console.WriteLine("All threads fired.");
        m_dtStart = DateTime.Now;
        m_event.Set();

        while (true)
        {
            Thread.Sleep(1000);
            Console.Clear();
            Console.WriteLine($"Iterations/sec: { (int)(m_iNumOfIterations / (DateTime.Now - m_dtStart).TotalSeconds) }, Ave rented size: { (m_dAvgSizeRatio * 100d).ToString("0.0") }%, exceptions: { m_iNumOfExceptions }.");
        }
    }
}