Use of object pool in. NET Core

Keywords: Database .NET microsoft

1, What is an object pool

Object pool is simply a software design idea that provides reusable ability for objects. We often say that it is not difficult to borrow and return, and the object pool ensures that objects can be reused through borrowing and returning, so as to save the performance overhead of frequent object creation. The most commonly used scene of object pool is game design, because there are a large number of reusable objects in the game, and the continuous emergence of bullets is not recycled. There is something called connection pool in the database. Whenever the database cannot be connected, experienced developers often check whether the connection pool is full. In fact, this is the specific implementation of the object pool pattern in a specific field. Therefore, an object pool is essentially a container responsible for the creation and destruction of a set of objects. The biggest advantage of object pool is that it can independently manage each object in the pool and decide whether they need to be recycled or reused. We all know that creating a new object requires certain system resources. Once these objects can be reused, we can save system resource overhead, which will be very helpful to improve system performance. The following code implements a simple object pool implemented in Microsoft official documents:

public class ObjectPool<T> : IObjectPool<T>
{
	private Func<T> _instanceFactory;
	private ConcurrentBag<T> _instanceItems;
	public ObjectPool(Func<T> instanceFactory)
	{
		_instanceFactory = instanceFactory ?? 
		throw new ArgumentNullException(nameof(instanceFactory));
		_instanceItems = new ConcurrentBag<T>();
	}
	public T Get()
	{
		T item;
		if (_instanceItems.TryTake(out item)) return item;
		return _instanceFactory();
	}
	public void Return(T item)
	{
		_instanceItems.Add(item);
	}
}

2, Object pool in. NET Core

In. NET Core, Microsoft has provided us with the implementation of object pool, namely Microsoft.Extensions.ObjectPool. It mainly provides three core components: ObjectPool, ObjectPoolProvider and IPooledObjectPolicy. ObjectPool is an abstract class that provides two methods: Get and Return. This is called borrowing and returning. ObjectPoolProvider is also an abstract class. Its responsibility is to Create ObjectPool. It provides two Create methods. The difference between the two is that the parameterless version essentially uses DefaultPooledObjectPolicy. It, DefaultObjectPool and DefaultObjectPoolProvider are the default implementations provided by Microsoft. IPooledObjectPolicy can define different policies for different object pools to determine how objects can be borrowed and returned. DefaultObjectPool uses ObjectWrapper [] internally to manage objects. The size of ObjectWrapper [] is equal to maximumretrieved-1. By default, maximumretrieved is equal to Environment.ProcessorCount * 2. The Interlocked.CompareExchange() method is mainly used here. The specific code is as follows:

public override T Get()
{
  var item = _firstItem;
  if (item == null || Interlocked.CompareExchange(ref _firstItem, null, item) != item)
  {
    var items = _items;
    for (var i = 0; i < items.Length; i++)
    {
      item = items[i].Element;
      if (item != null && Interlocked.CompareExchange(ref items[i].Element, null, item) == item)
      {
        return item;
      }
    }
    item = Create();
  }
  return item;
}
// Non-inline to improve its code quality as uncommon path
[MethodImpl(MethodImplOptions.NoInlining)]
private T Create() => _fastPolicy?.Create() ?? _policy.Create();

public override void Return(T obj)
{
  if (_isDefaultPolicy || (_fastPolicy?.Return(obj) ?? _policy.Return(obj)))
  {
    if (_firstItem != null || Interlocked.CompareExchange(ref _firstItem, obj, null) != null)
    {
      var items = _items;
      for (var i = 0; i < items.Length && Interlocked.CompareExchange(ref items[i].Element, obj, null) != null; ++i)
      {

      }
    }
  }
}

Here, the Interlocked.CompareExchange() method is used. The Get() method exchanges items[i].Element with null, sets the specified element to null, and returns the original value. The value of items[i].Element and obj exchanged by the Return() method is not null, indicating that the specified element has been returned. This method will exchange only when the first parameter and the third parameter are equal.
After all, let's take a look at the specific usage of object pool:

var service = new ServiceCollection();

//Use DefaultObjectPoolProvider
service.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
//Use default policy
service.AddSingleton<ObjectPool<Foo>>(serviceProvider =>
{
  var objectPoolProvider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
  return objectPoolProvider.Create<Foo>();
});
//Use custom policy
service.AddSingleton<ObjectPool<Foo>>(serviceProvider =>
{
  var objectPoolProvider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
  return objectPoolProvider.Create(new FooObjectPoolPolicy());
});

var serviceProvider = _service.BuildServiceProvider();

var objectPool = _serviceProvider.GetService<ObjectPool<Foo>>();

//Borrow and return, the same object twice
var item1 = objectPool.Get();
objectPool.Return(item1);
var item2 = objectPool.Get();
Assert.AreEqual(item1, item2);//true

//Two times are different objects
var item3 = objectPool.Get();
var item4 = objectPool.Get();
Assert.AreEqual(item3, item4);//false

In the above code, Foo and FooObjectPoolPolicy are two tool classes:

public class Foo
{
  public string Id { get; set; }
  public DateTime? CreatedAt { get; set; }
  public string CreatedBy { get; set; }
}

public class FooObjectPoolPolicy : IPooledObjectPolicy<Foo>
{
  public Foo Create()
  {
    return new Foo()
    {
      Id = Guid.NewGuid().ToString("N"),
      CreatedAt = DateTime.Now,
      CreatedBy = "zs"
    };
  }

  public bool Return(Foo obj)
  {
    return true;
  }
}

TIP: when you need to control how objects in the object pool are created, you can consider implementing a custom ipooledobjectpolicy < T >, on the contrary, the implementation of defaultpooledobjectpolicy < T > can fully meet your needs.

3, Summary of this paper

The implementation of object pool can consider many data structures such as ConcurrentBag, Stack, Queue and BlockingCollection. Microsoft has implemented a simple object pool for us in the. NET Core. In most cases, we only need to define our own IPooledObjectPolicy to determine how to borrow and return objects. In short, GameObject in the game world and connection pool in the database are the concrete implementation of object pool mode in their respective fields.

TIP: object pool is a software design mode that reduces resource overhead and improves system performance by reusing objects. Its core is to control the life cycle of objects in the container to avoid active recovery of the system. Objects borrowed from the object pool must be returned in time. Otherwise, there will be no available resources in the object pool.

Posted by ChaosDream on Thu, 18 Nov 2021 07:50:30 -0800