1. ホーム
  2. Web プログラミング
  3. ASP.NET

.NET Coreでオブジェクトプールを使用する

2022-01-13 14:19:36

<スパン I. オブジェクトプールとは

<ブロッククオート

オブジェクトプールとは、簡単に言えば、オブジェクトに再利用性を持たせるためのソフトウェア設計の考え方である。よく「オブジェクトを借りて返すのは簡単だ」と言いますが、オブジェクトプーリングは、オブジェクトを借りて返すという2つの動作で再利用できるようにすることで、オブジェクトを頻繁に生成することによるパフォーマンスのオーバーヘッドを節約する方法なのです。オブジェクトプーリングの最も一般的なシナリオはゲームデザインです。ゲームでは、再利用可能なオブジェクトが多数存在し、一定量の弾丸が出現し、周期的に再生成されることはないからです。データベースにはコネクションプールと呼ばれるものがあり、データベースへの接続に失敗すると、経験豊富な開発者はまずコネクションプールが満杯かどうかを確認する傾向があります。これは実は、特定のドメインにおけるオブジェクトプーリングパターンの具体的な実装なのです。つまり、オブジェクトプールとは本質的に、一連のオブジェクトの生成と破棄を担当するコンテナなのだ。オブジェクト・プールの最大の利点は、プール内の各オブジェクトを自律的に管理し、リサイクルする必要があるか、再利用可能かどうかを判断できることです。新しいオブジェクトを作成するとシステム・リソースを消費することは周知の事実ですが、これらのオブジェクトを再利用できれば、システム・リソースのオーバーヘッドを節約でき、システムのパフォーマンス向上に非常に役立ちます。次のコードは、Microsoftの公式ドキュメントにあるシンプルなオブジェクトプールを実装したものです。


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);

	}

}



NET Coreのオブジェクトプーリング

での NET Core マイクロソフトは、オブジェクトプーリングの実装を Microsoft.Extensions.ObjectPool . これは、次の3つの主要なコアコンポーネントを提供します。

ObjectPool
ObjectPoolProvider
and
IPooledObjectPolicy
.
ObjectPool
is an abstract class that provides external methods Get and Return, which are called borrow and return.
ObjectPoolProvider
is also an abstract class whose responsibility is to create ObjectPool, it provides two Create methods, the difference between the two is that the parameterless version essentially uses the
DefaultPooledObjectPolicy
. It is the same as the
DefaultObjectPool
DefaultObjectPool uses an ObjectWrapper[] to manage objects internally. The size of the ObjectWrapper[] is equal to maximumRetained-1, and by default maximumRetained is equal to
Environment.ProcessorCount * 2
The main use of
Interlocked.CompareExchange()
method.
The specific code is as follows.
public override T Get()

{

  var item = _firstItem;

  CompareExchange(ref _firstItem, null, item) if (item == null || Interlocked. = 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;



public override void Return(T obj)

{

  Return(obj) if (_isDefaultPolicy || (_fastPolicy?. _policy.Return(obj)))

  {

    if (_firstItem ! = 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 we use
Interlocked.CompareExchange()
method, the Get() method will
items[i].Element
and
null
The Return() method swaps the specified element to null and returns the original value.
items[i].Element
and obj are swapped to a value other than null, indicating that the specified element has been returned. This method will only swap if the first and third arguments are equal.
Having said that, let's look at the specific usage of the object pool.
var service = new ServiceCollection();

//use DefaultObjectPoolProvider

service.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();

// Use the default policy

service.AddSingleton<ObjectPool<Foo>>(serviceProvider =>

{

  var objectPoolProvider = serviceProvider.GetRequiredService<ObjectPoolProvider>();

  return objectPoolProvider.Create<Foo>();

});

//Use a 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>>();



// there is a borrow and a return, twice is the same object

var item1 = objectPool.Get();

Get(); objectPool.Return(item1);

var item2 = objectPool.Get();

Assert.AreEqual(item1, item2);/


// there is a borrowing but not a return, two different objects

var item3 = objectPool.Get();

var item4 = objectPool.Get();

Assert.AreEqual(item3, item4);/


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 within the object pool are created, you might consider implementing a custom
IPooledObjectPolicy
and vice versa
DefaultPooledObjectPolicy
implementation is perfectly fine for your use.
III. Summary of this article
Implementing an object pool can be considered
ConcurrentBag
ConcurrentBag, Stack.
Queue
and
BlockingCollection
NET Core, Microsoft has implemented a simple object pool for us, and in most cases, we only need to define our own
{NET Core.
IPooledObjectPolicy

GameObject

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;

  }

}



DefaultPooledObjectPolicy

で、オブジェクトの貸し借りを決定します。要するに、ゲームの世界である GameObject とデータベースのコネクションプールは、それぞれのドメインにおけるオブジェクトプーリングパターンの具体的な実装である。

<ブロッククオート

TIP オブジェクトプーリングは、オブジェクトを再利用することでリソースのオーバーヘッドを減らし、その結果システムのパフォーマンスを向上させるソフトウェアのデザインパターンである。その核心は、システムによる積極的なリサイクルを回避するためにコンテナ内のオブジェクトのライフサイクルを制御することであり、オブジェクトプールから借りたオブジェクトは適時に返却しなければならず、さもなければオブジェクトプールに利用可能なリソースが存在しないことになる。

NET Core Object Poolのサポートは、過去の記事を検索するか、以下の関連記事を引き続き閲覧してください!