原则上所有的引用类型对象都可以通过对象池来提供,但是在具体的应用中需要权衡是否值得用。虽然对象池能够通过对象复用的方式避免GC,但是它存储的对象会耗用内存,如果对象复用的频率很小,使用对象池是不值的。如果某个小对象的使用周期很短,能够确保GC在第0代就能将其回收,这样的对象其实也不太适合放在对象池中,因为第0代GC的性能其实是很高的。除此之外,对象释放到对象池之后就有可能被其他线程提取出来,如果释放的时机不对,有可能造成多个线程同时操作同一个对象。总之,我们在使用之前得考虑当前场景是否适用对象池,在使用的时候严格按照“有借有还”、“不用才还”的原则。
一、池化集合
我们知道一个List
接下来我们通过一个简单的实例来演示一下如何采用对象池的方式来提供一个List
public class FoobarListPolicy : PooledObjectPolicy> { private readonly int _initCapacity; private readonly int _maxCapacity; public FoobarListPolicy(int initCapacity, int maxCapacity) { _initCapacity = initCapacity; _maxCapacity = maxCapacity; } public override List
Create() => new List (_initCapacity); public override bool Return(List obj) { if(obj.Capacity <= _maxCapacity) { obj.Clear(); return true; } return false; } } public class Foobar { public int Foo { get; } public int Bar { get; } public Foobar(int foo, int bar) { Foo = foo; Bar = bar; } }
如代码片段所示,我们在FoobarListPolicy类型中定义了两个字段,_initCapacity字段表示列表创建时指定的初始容量,另一个_maxCapacity则表示对象池存储列表的最大容量。之所以要限制列表的最大容量,是为了避免复用几率很少的大容量列表常驻内存。在实现的Create方法中,我们利用初始容量创建出List
class Program { static void Main() { var objectPool = new ServiceCollection() .AddSingleton() .BuildServiceProvider() .GetRequiredService () .Create(new FoobarListPolicy(1024, 1024*1024)); string json; var list = objectPool.Get(); try { list.AddRange(Enumerable.Range(1, 1000).Select(it => new Foobar(it, it))); json = JsonConvert.SerializeObject(list); } finally { objectPool.Return(list); } } }
二、池化StringBuilder
我们知道,如果频繁涉及针对字符串拼接的操作,应该使用StringBuilder以获得更好的性能。实际上,StringBuilder对象自身也存在类似于列表对象的扩容问题,所以最好的方式就是利用对象池的方式来复用它们。对象池框架针对StringBuilder对象的池化提供的原生支持,我们接下来通过一个简单的示例来演示具体的用法。
class Program { static void Main() { var objectPool = new ServiceCollection() .AddSingleton() .BuildServiceProvider() .GetRequiredService () .CreateStringBuilderPool(1024, 1024*1024); var builder = objectPool.Get(); try { for (int index = 0; index < 100; index++) { builder.Append(index); } Console.WriteLine(builder); } finally { objectPool.Return(builder); } } }
如上面的代码片段所示,我们直接可以调用ObjectPoolProvider的CreateStringBuilderPool扩展方法就可以得到针对StringBuilder的对象池对象(类型为ObjectPool
public class StringBuilderPooledObjectPolicy : PooledObjectPolicy{ public int InitialCapacity { get; set; } = 100; public int MaximumRetainedCapacity { get; set; } = 4 * 1024; public override StringBuilder Create()=> new StringBuilder(InitialCapacity); public override bool Return(StringBuilder obj) { if (obj.Capacity > MaximumRetainedCapacity) { return false; } obj.Clear(); return true; } }
可以看出它的定义和我们前面定义的FoobarListPolicy类型如出一辙。在默认情况下,池化StringBuilder对象的初始化和最大容量分别为100和5096。如下所示的是ObjectPoolProvider用于创建ObjectPool
public static class ObjectPoolProviderExtensions { public static ObjectPoolCreateStringBuilderPool( this ObjectPoolProvider provider) => provider.Create(new StringBuilderPooledObjectPolicy()); public static ObjectPool CreateStringBuilderPool( this ObjectPoolProvider provider, int initialCapacity, int maximumRetainedCapacity) { var policy = new StringBuilderPooledObjectPolicy() { InitialCapacity = initialCapacity, MaximumRetainedCapacity = maximumRetainedCapacity, }; return provider.Create(policy); } }
三、ArrayPool
接下来介绍的和前面的内容没有什么关系,但同属于我们常用对象池使用场景。我们在编程的时候会大量使用到集合,集合类型(像基于链表的集合除外)很多都采用一个数组作为内部存储,所以会有前面所说的扩容问题。如果这个数组很大,还会造成GC的压力。我们在前面已经采用池化集合的方案解决了这个问题,其实这个问题还有另外一种解决方案。
在很多情况下,当我们需要创建一个对象的时候,实际上需要的一段确定长度的连续对象序列。假设我们将数组对象进行池化,当我们需要一段定长的对象序列的时候,从池中提取一个长度大于所需长度的可用数组,并从中截取可用的连续片段(一般从头开始)就可以了。在使用完之后,我们无需执行任何的释放操作,直接将数组对象归还到对象池中就可以了。这种基于数组的对象池使用方式可以利用ArrayPool
public abstract class ArrayPool{ public abstract T[] Rent(int minimumLength); public abstract void Return(T[] array, bool clearArray); public static ArrayPool Create(); public static ArrayPool Create(int maxArrayLength, int maxArraysPerBucket); public static ArrayPool Shared { get; } }
如上面的代码片段所示,抽象类型ArrayPool
我们可以通过静态方法Create创建一个ArrayPool
class Program { static async Task Main() { using var fs = new FileStream("test.txt", FileMode.Open); var length = (int)fs.Length; var bytes = ArrayPool.Shared.Rent(length); try { await fs.ReadAsync(bytes, 0, length); Console.WriteLine(Encoding.Default.GetString(bytes, 0, length)); } finally { ArrayPool .Shared.Return(bytes); } } }
四、MemoryPool
数组是对托管堆中用于存储同类对象的一段连续内存的表达,而另一个类型Memory
public abstract class MemoryPool: IDisposable { public abstract int MaxBufferSize { get; } public static MemoryPool Shared { get; } public void Dispose(); protected abstract void Dispose(bool disposing); public abstract IMemoryOwner Rent(int minBufferSize = -1); } public interface IMemoryOwner : IDisposable { Memory Memory { get; } }
MemoryPool
class Program { static async Task Main() { using var fs = new FileStream("test.txt", FileMode.Open); var length = (int)fs.Length; using (var memoryOwner = MemoryPool.Shared.Rent(length)) { await fs.ReadAsync(memoryOwner.Memory); Console.WriteLine(Encoding.Default.GetString( memoryOwner.Memory.Span.Slice(0,length))); } } }
.NET Core对象池的应用:编程篇
.NET Core对象池的应用:设计篇
到此这篇关于.NET Core对象池的应用:扩展篇的文章就介绍到这了,更多相关.NET Core对象池的应用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!