请选择 进入手机版 | 继续访问电脑版

[ASP.NET] .NET Core对象池的应用:扩展篇

[复制链接]
查看39 | 回复11 | 2021-9-15 06:51:44 | 显示全部楼层 |阅读模式
目次

       原则上全部 的引用范例 对象都可以通过对象池来提供,但是在详细 的应用中必要 衡量 是否值得用。固然 对象池可以或许 通过对象复用的方式避免GC,但是它存储的对象会耗用内存,假如 对象复用的频率很小,使用 对象池是不值的。假如 某个小对象的使用 周期很短,可以或许 确保GC在第0代就能将其回收,如许 的对象着实 也不太得当 放在对象池中,由于 第0代GC的性能着实 是很高的。除此之外,对象开释 到对象池之后就有大概 被其他线程提取出来,假如 开释 的机遇 不对,有大概 造成多个线程同时操作同一个对象。总之,我们在使用 之前得思量 当前场景是否实用 对象池,在使用 的时间 严格 按照“有借有还”、“不用才还”的原则。

一、池化集合

我们知道一个List对象内部会使用 一个数组来保存列表元素。数组是定长的,以是 List有一个最大容量(表现 为它的Capacity属性)。当列表元素数目 超过数组容量时,必须对列表对象举行 扩容,即创建一个新的数组并将现有的元素拷贝进去。当前元素越多,必要 实验 的拷贝操作就越多,对性能的影响天然 就越大。假如 我们创建List对象,并在此中 不断地添加对象,有大概 会导致多次扩容,以是 假如 可以或许 预知元素数目 ,我们在创建List对象时应该指定一个合适的容量。但是很多环境 下,列表元素数目 是动态变化的,我们可以使用 对象池来办理 这个标题 。

接下来我们通过一个简单的实例来演示一下怎样 采用对象池的方式来提供一个List对象,元素范例 Foobar如下所示。为了可以或许 显式控制列表对象的创建和归还,我们自定义了如下这个表示池化对象策略的FoobarListPolicy。通过《计划 篇》针对对象池默认实现的先容 ,我们知道直接继承PooledObjectPolicy范例 比实现IPooledObjectPolicy接口具有更好的性能上风 。

  1. public class FoobarListPolicy : PooledObjectPolicy<List<Foobar>>
  2. {
  3. private readonly int _initCapacity;
  4. private readonly int _maxCapacity;
  5. public FoobarListPolicy(int initCapacity, int maxCapacity)
  6. {
  7. _initCapacity = initCapacity;
  8. _maxCapacity = maxCapacity;
  9. }
  10. public override List<Foobar> Create() => new List<Foobar>(_initCapacity);
  11. public override bool Return(List<Foobar> obj)
  12. {
  13. if(obj.Capacity <= _maxCapacity)
  14. {
  15. obj.Clear();
  16. return true;
  17. }
  18. return false;
  19. }
  20. }
  21. public class Foobar
  22. {
  23. public int Foo { get; }
  24. public int Bar { get; }
  25. public Foobar(int foo, int bar)
  26. {
  27. Foo = foo;
  28. Bar = bar;
  29. }
  30. }
复制代码

如代码片断 所示,我们在FoobarListPolicy范例 中定义了两个字段,_initCapacity字段表示列表创建时指定的初始容量,另一个_maxCapacity则表示对象池存储列表的最大容量。之以是 要限定 列表的最大容量,是为了避免复用几率很少的大容量列表常驻内存。在实现的Create方法中,我们使用 初始容量创建出List对象。在Return方法中,我们先将待回归的列表清空,然后根据其当前容量决定是否要将其开释 到对象池。下面的程序演示了采用对象池的方式来提供List列表。如代码片断 所示,我们在调用ObjectPoolProvider对象的Create创建代表对象池的ObjectPool对象时,指定了作为池化对象策略的FoobarListPolicy对象。我们将初始和最大容量设置成1K(1024)和1M(1024*1024)。我们使用 对象池提供了一个List对象,并在此中 添加了10000个元素。假如 这段代码实验 的频率很高,对团体 的性能是有提拔 的。

  1. class Program
  2. {
  3. static void Main()
  4. {
  5. var objectPool = new ServiceCollection()
  6. .AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()
  7. .BuildServiceProvider()
  8. .GetRequiredService<ObjectPoolProvider>()
  9. .Create(new FoobarListPolicy(1024, 1024*1024));
  10. string json;
  11. var list = objectPool.Get();
  12. try
  13. {
  14. list.AddRange(Enumerable.Range(1, 1000).Select(it => new Foobar(it, it)));
  15. json = JsonConvert.SerializeObject(list);
  16. }
  17. finally
  18. {
  19. objectPool.Return(list);
  20. }
  21. }
  22. }
复制代码

二、池化StringBuilder

我们知道,假如 频仍 涉及针对字符串拼接的操作,应该使用 StringBuilder以获得更好的性能。实际 上,StringBuilder对象自身也存在类似 于列表对象的扩容标题 ,以是 最好的方式就是使用 对象池的方式来复用它们。对象池框架针对StringBuilder对象的池化提供的原生支持,我们接下来通过一个简单的示例来演示详细 的用法。

  1. class Program
  2. {
  3. static void Main()
  4. {
  5. var objectPool = new ServiceCollection()
  6. .AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()
  7. .BuildServiceProvider()
  8. .GetRequiredService<ObjectPoolProvider>()
  9. .CreateStringBuilderPool(1024, 1024*1024);
  10. var builder = objectPool.Get();
  11. try
  12. {
  13. for (int index = 0; index < 100; index++)
  14. {
  15. builder.Append(index);
  16. }
  17. Console.WriteLine(builder);
  18. }
  19. finally
  20. {
  21. objectPool.Return(builder);
  22. }
  23. }
  24. }
复制代码

如上面的代码片断 所示,我们直接可以调用ObjectPoolProvider的CreateStringBuilderPool扩展方法就可以得到针对StringBuilder的对象池对象(范例 为ObjectPool)。我们上面演示实例一样,我们指定的也是StringBuilder对象的初始和最大容量。池化StringBuilder对象的核心表现 在对应的策略范例 上,即如下这个StringBuilderPooledObjectPolicy范例 。

  1. public class StringBuilderPooledObjectPolicy : PooledObjectPolicy<StringBuilder>
  2. {
  3. public int InitialCapacity { get; set; } = 100;
  4. public int MaximumRetainedCapacity { get; set; } = 4 * 1024;
  5. public override StringBuilder Create()=> new StringBuilder(InitialCapacity);
  6. public override bool Return(StringBuilder obj)
  7. {
  8. if (obj.Capacity > MaximumRetainedCapacity)
  9. {
  10. return false;
  11. }
  12. obj.Clear();
  13. return true;
  14. }
  15. }
复制代码

可以看出它的定义和我们前面定义的FoobarListPolicy范例 如出一辙。在默认环境 下,池化StringBuilder对象的初始化和最大容量分别为100和5096。如下所示的是ObjectPoolProvider用于创建ObjectPool对象的两个CreateStringBuilderPool扩展方法的定义。

  1. public static class ObjectPoolProviderExtensions
  2. {
  3. public static ObjectPool<StringBuilder> CreateStringBuilderPool( this ObjectPoolProvider provider)
  4. => provider.Create(new StringBuilderPooledObjectPolicy());
  5. public static ObjectPool<StringBuilder> CreateStringBuilderPool( this ObjectPoolProvider provider, int initialCapacity, int maximumRetainedCapacity)
  6. {
  7. var policy = new StringBuilderPooledObjectPolicy()
  8. {
  9. InitialCapacity = initialCapacity,
  10. MaximumRetainedCapacity = maximumRetainedCapacity,
  11. };
  12. return provider.Create(policy);
  13. }
  14. }
复制代码

三、ArrayPool

接下来先容 的和前面的内容没有什么关系,但同属于我们常用对象池使用 场景。我们在编程的时间 会大量使用 到集合,集合范例 (像基于链表的集合除外)很多都采用一个数组作为内部存储,以是 会有前面所说的扩容标题 。假如 这个数组很大,还会造成GC的压力。我们在前面已经采用池化集合的方案办理 了这个标题 ,着实 这个标题 还有别的 一种办理 方案。

在很多环境 下,当我们必要 创建一个对象的时间 ,实际 上必要 的一段确定长度的连续 对象序列。假设我们将数组对象举行 池化,当我们必要 一段定长的对象序列的时间 ,从池中提取一个长度大于所需长度的可用数组,并从中截取可用的连续 片断 (一样平常 从头开始)就可以了。在使用 完之后,我们无需实验 任何的开释 操作,直接将数组对象归还到对象池中就可以了。这种基于数组的对象池使用 方式可以使用 ArrayPool来实现。

  1. public abstract class ArrayPool<T>
  2. {
  3. public abstract T[] Rent(int minimumLength);
  4. public abstract void Return(T[] array, bool clearArray);
  5. public static ArrayPool<T> Create();
  6. public static ArrayPool<T> Create(int maxArrayLength, int maxArraysPerBucket);
  7. public static ArrayPool<T> Shared { get; }
  8. }
复制代码

如上面的代码片断 所示,抽象范例 ArrayPool同样提供了完成对象池两个基本操作的方法,此中 Rent方法从对象池中“借出”一个不小于(不是等于)指定长度的数组,该数组终极 通过Return方法开释 到对象池。Return方法的clearArray参数表示在归还数组之前是否要将其清空,这取决我们针对数组的使用 方式。假如 我们每次都必要 覆盖原始的内容,就没有必要额外实验 这种多余操作。

我们可以通过静态方法Create创建一个ArrayPool对象。池化的数组并未直接存储在对象池中,长度靠近 的多个数组会被封装成一个桶(Bucket)中,如许 的好处是在实验 Rent方法的时间 可以根据指定的长度快速找到最为匹配的数组(大于并靠近 指定的长度)。对象池存储的是一组Bucket对象,答应 的数组长度越大,桶的数目 越多。Create方法除了可以指定数组答应 最大长度,还可以指定每个桶的容量。除了调用静态Create方法创建一个独占使用 的ArrayPool对象之外,我们可以使用 静态属性Shared返回一个应用范围内共享的ArrayPool对象。ArrayPool的使用 非常方便,如下的代码片断 演示了一个读取文件的实例。

  1. class Program
  2. {
  3. static async Task Main()
  4. {
  5. using var fs = new FileStream("test.txt", FileMode.Open);
  6. var length = (int)fs.Length;
  7. var bytes = ArrayPool<byte>.Shared.Rent(length);
  8. try
  9. {
  10. await fs.ReadAsync(bytes, 0, length);
  11. Console.WriteLine(Encoding.Default.GetString(bytes, 0, length));
  12. }
  13. finally
  14. {
  15. ArrayPool<byte>.Shared.Return(bytes);
  16. }
  17. }
  18. }
复制代码

四、MemoryPool

数组是对托管堆中用于存储同类对象的一段连续 内存的表达,而另一个范例 Memory则具有更加广泛的应用,由于 它不仅仅可以表示一段连续 的托管(Managed)内存,还可以表示一段连续 的Native内存,以致 线程堆栈内存。具有如下定义的MemoryPool表示针对Memory范例 的对象池。

  1. public abstract class MemoryPool<T> : IDisposable
  2. {
  3. public abstract int MaxBufferSize { get; }
  4. public static MemoryPool<T> Shared { get; }
  5. public void Dispose();
  6. protected abstract void Dispose(bool disposing);
  7. public abstract IMemoryOwner<T> Rent(int minBufferSize = -1);
  8. }
  9. public interface IMemoryOwner<T> : IDisposable
  10. {
  11. Memory<T> Memory { get; }
  12. }
复制代码

MemoryPool和ArrayPool具有类似 的定义,比如通过静态属性Shared获取当前应用全局共享的MemoryPool对象,通过Rent方法从对象池中借出一个不小于指定大小的Memory对象。不同的是,MemoryPool的Rent方法并没有直接返回一个Memory对象,而是一个封装了该对象的IMemoryOwner对象。MemoryPool也没有定义一个用来开释 Memory对象的Reurn方法,这个操作是通过IMemoryOwner对象的Dispose方法完成的。假如 采用MemoryPool,前面针对ArrayPool的演示实例可以改写成如下的情势 。

  1. class Program
  2. {
  3. static async Task Main()
  4. {
  5. using var fs = new FileStream("test.txt", FileMode.Open);
  6. var length = (int)fs.Length;
  7. using (var memoryOwner = MemoryPool<byte>.Shared.Rent(length))
  8. {
  9. await fs.ReadAsync(memoryOwner.Memory);
  10. Console.WriteLine(Encoding.Default.GetString( memoryOwner.Memory.Span.Slice(0,length)));
  11. }
  12. }
  13. }
复制代码

.NET Core对象池的应用:编程篇

.NET Core对象池的应用:计划 篇

到此这篇关于.NET Core对象池的应用:扩展篇的文章就先容 到这了,更多干系 .NET Core对象池的应用内容请搜索 脚本之家从前 的文章或继续欣赏 下面的干系 文章渴望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

avatar 阿拉斯加他爸爸 | 2021-9-15 08:33:20 | 显示全部楼层
admin楼主的帖子提神醒脑啊!
回复

使用道具 举报

avatar 祖国统一富强 | 2021-9-16 10:42:01 | 显示全部楼层
admin楼主,我告诉你一个你不知道的的秘密,有一个牛逼的网站,他卖的服务器是永久的,我们的网站用 服务器都是在这家买的,你可以去试试。访问地址:http://fwq.mxswl.com
回复

使用道具 举报

avatar 123457264 | 2021-9-16 10:50:37 | 显示全部楼层
admin楼主病的不轻啊!
回复

使用道具 举报

avatar 123456848 | 2021-9-20 00:30:43 | 显示全部楼层
看了这么多帖子,第一次看看到这么有内涵的!
回复

使用道具 举报

avatar 聚雅阁砚堂 | 2021-9-20 19:24:31 | 显示全部楼层
admin楼主的帖子越来越有深度了!
回复

使用道具 举报

avatar 无热天龙中 | 2021-9-21 08:55:48 | 显示全部楼层
有钱、有房、有车,人人都想!
回复

使用道具 举报

avatar 北京愤怒羔羊猩 | 2021-10-5 10:32:55 | 显示全部楼层
看在admin楼主的面子上,认真回帖!
回复

使用道具 举报

avatar 我是一个梦蛋 | 2021-10-6 01:09:16 | 显示全部楼层
太邪乎了吧?
回复

使用道具 举报

avatar 白刃玄衣及 | 2021-10-8 23:49:07 | 显示全部楼层
楼上的说的很好!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则