posts - 19 , comments - 8 , trackbacks - 0

Array pooling in C#

Introduction

The following class reduces garbage collection by maintaining a pool of allocated arrays.

DisposableValue

I wanted a simple way to manage the lifetime of the arrays. To do this I have borrowed an idea from reactive extensions

using System;

namespace JetBlack.Common
{
    public class DisposableValue<T> : IDisposable
    {
        private readonly Action _dispose;

        public DisposableValue(T value, Action  dispose)
        {
            _dispose = dispose;
            Value = value;
        }

        public T Value { get; private set; }

        public void Dispose()
        {
            _dispose();
        }

        public static DisposableValue<T> Create(T value, Action dispose)
        {
            return new DisposableValue<T>(value, dispose);
        }
    }
}

ArrayPool

The actual array pool is also quite simple. The public interface provides an allocater, a resizer, a way to clear down the cache, and a field representing the empty array. This is a minor optimisation, as an empty array is immutable we only need one.

using System;
using System.Collections.Generic;

namespace JetBlack.Common
{
    public class ArrayPool<T>
    {
        private readonly Dictionary<int, Stack<T[]>> _pool = new Dictionary<int,Stack<T[]>>();

        public readonly T[] Empty = new T[0];

        public DisposableValue<T[]> AllocateDisposable(int size)
        {
            var array = Allocate(size);
            return DisposableValue<T[]>.Create(array, () => Free(array));
        }

        public DisposableValue<T[]> Resize(DisposableValue<T[]> source, int size)
        {
            if (size < 0) throw new ArgumentOutOfRangeException("size", "Must be positive.");

            var dest = AllocateDisposable(size);
            Array.Copy(source.Value, dest.Value, size < source.Value.Length ? size : source.Value.Length);
            source.Dispose();
            return dest;
        }

        public virtual void Clear()
        {
            _pool.Clear();
        }

        internal virtual T[] Allocate(int size)
        {
            if (size < 0) throw new ArgumentOutOfRangeException("size", "Must be positive.");
            
            if (size == 0) return Empty;

            Stack<T[]> candidates;
            return _pool.TryGetValue(size, out candidates) && candidates.Count > 0 ? candidates.Pop() : new T[size];
        }

        internal virtual void Free(T[] array)
        {
            if (array == null) throw new ArgumentNullException("array");
            
            if (array.Length == 0) return;

            Stack<T[]> candidates;
            if (!_pool.TryGetValue(array.Length, out candidates))
                _pool.Add(array.Length, candidates = new Stack<T[]>());
            candidates.Push(array);
        }
    }
}

Concurrency

Concurrent support is fairly straightforward.

namespace JetBlack.Common
{
    public class ConcurrentArrayPool<T> : ArrayPool<T>
    {
        internal override T[] Allocate(int size)
        {
            lock (this)
            {
                return base.Allocate(size);
            }
        }

        internal override void Free(T[] array)
        {
            lock (this)
            {
                base.Free(array);
            }
        }

        public override void Clear()
        {
            lock (this)
            {
                base.Clear();
            }
        }
    }
}

Print | posted on Thursday, December 18, 2014 3:57 PM | Filed Under [ C# array ArrayPool ]

Feedback

Gravatar

# re: Array pooling in C#

Your locking semantics are not best practice. https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/lock-statement
6/2/2017 4:29 PM | reader
Post A Comment
Title:
Name:
Email:
Comment:
Verification:
 

Powered by: