经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
实现一个极简的字节数组对象池
来源:cnblogs  作者:Artech  时间:2023/11/3 9:04:00  对本文有异议

.NET利用ArrayPoolPool<T>和MemoryPool<T>提供了针对Array/Memory<T>的对象池功能。最近在一个项目中需要使用到针对字节数组的对象池,由于这些池化的字节数组相当庞大,我希望将它们分配到POH上以降低GC的压力。由于ArrayPoolPool<T>没法提供支持,所以我提供了一个极简的实现。

目录
一、Bucket
二、ByteArrayOwner
三、ByteArrayPool
四、测试

一、Bucket

和大部分实现方案一样,我需要限制池化数组的最大尺寸,同时设置最小长度为16。我们将[16-MaxLength]划分为N个区间,每个区间对应一个Bucket,该Bucket用来管理“所在长度区间”的字节数组。如下所示的就是这个Bucket类型的定义:我们利用一个ConcurrentBag<byte[]>来维护池化的字节数组,数组的“借”与“还”由TryTake和Add方法来实现。

  1. internal sealed class Bucket
  2. {
  3. private readonly ConcurrentBag<byte[]> _byteArrays = new();
  4. public void Add(byte[] array) => _byteArrays.Add(array);
  5. public bool TryTake([MaybeNullWhen(false)] out byte[] array) => _byteArrays.TryTake(out array);
  6. }

二、ByteArrayOwner

从对象池“借出”的是一个ByteArrayOwner 对象,它是对字节数组和所在Bucket的封装。如果指定的数组长度超过设置的阈值,意味着Bucket不存在,借出的字节数组也不需要还回去,这一逻辑体现在IsPooled属性上。ByteArrayOwner 实现了IDisposable接口,实现Dispose方法调用Bucket的Add方法完成了针对字节数组的“归还”,该方法利用针对_isReleased字段的CompareExchange操作解决“重复归还”的问题。

  1. public sealed class ByteArrayOwner : IDisposable
  2. {
  3. private readonly byte[] _bytes;
  4. private readonly Bucket? _bucket;
  5. private volatile int _isReleased;
  6. public bool IsPooled => _bucket is not null;
  7. internal ByteArrayOwner(byte[] bytes, Bucket? bucket)
  8. {
  9. _bytes = bytes;
  10. _bucket = bucket;
  11. }
  12. public byte[] Bytes => _isReleased == 0 ? _bytes : throw new ObjectDisposedException("The ByteArrayOwner has been released.");
  13. public void Dispose()
  14. {
  15. if (Interlocked.CompareExchange(ref _isReleased, 1, 0) == 0)
  16. {
  17. _bucket?.Add(_bytes);
  18. }
  19. }
  20. }

三、ByteArrayPool

具体的对象池实现体现在如下所示的ByteArrayPool类型上,池化数组的最大长度在构造函数中指定,ByteArrayPool据此划分长度区间并创建一组通过_buckets字段表示的Bucket数组。具体的区间划分实现在静态方法SelectBucketIndex方法中,当我们根据指定的数组长度确定具体Bucket的时候(对于Bucket在_buckets数组中的索引)同样调用此方法。另一个静态方法GetMaxSizeForBucket执行相反的操作,它根据指定的Bucket索引计算长度区间的最大值。当某个Bucket确定后,得到的数组都具有这个长度。作为ArrayPoolPool<T>的默认实现,ConfigurableArrayPool<T>也采用一样的算法。

  1. public sealed class ByteArrayPool
  2. {
  3. private readonly Bucket[] _buckets;
  4. public int MaxArrayLength { get; }
  5. public static ByteArrayPool Create(int maxLength) => new(maxLength);
  6. private ByteArrayPool(int maxLength)
  7. {
  8. var bucketCount = SelectBucketIndex(maxLength) + 1;
  9. _buckets = new Bucket[bucketCount];
  10. MaxArrayLength = GetMaxSizeForBucket(bucketCount - 1);
  11. for (int index = 0; index < bucketCount; index++)
  12. {
  13. _buckets[index] = new Bucket();
  14. }
  15. }
  16.  
  17. public ByteArrayOwner Rent(int minimumLength)
  18. {
  19. if (minimumLength < 0)
  20. {
  21. throw new ArgumentOutOfRangeException(nameof(minimumLength));
  22. }
  23. if (minimumLength > MaxArrayLength)
  24. {
  25. return new ByteArrayOwner(bytes: new byte[minimumLength], bucket: null);
  26. }
  27.  
  28. var bucketIndex = SelectBucketIndex(minimumLength);
  29. for (int index = bucketIndex; index < _buckets.Length; index++)
  30. {
  31. var bucket = _buckets[index];
  32. if (bucket.TryTake(out var array))
  33. {
  34. return new ByteArrayOwner(array, bucket: bucket);
  35. }
  36. }
  37.  
  38. return new ByteArrayOwner(bytes: GC.AllocateUninitializedArray<byte>(GetMaxSizeForBucket(bucketIndex), pinned: true), bucket: _buckets[bucketIndex]);
  39. }
  40.  
  41. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  42. public static int GetMaxSizeForBucket(int index) => 16 << index;
  43.  
  44. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  45. public static int SelectBucketIndex(int length) => BitOperations.Log2((uint)(length - 1) | 0xFu) - 3;
  46. }

在核心方法Rent中,如果指定的长度超过阈值,该方法会直接创建一个字节数组,并封装成返回的ByteArrayOwner 对象。由于这样的ByteArrayOwner 不具有对应的Bucket,所以不需要“归还”。如果指定的数组长度在允许的范围内,该方法会根据此长度确定对应Bucket的索引,并确定此索引对应的Bucket以及后续的Bucket是否保留着池化的数组,如果存在,直接将其封装成返回的ByteArrayOwner 对象。

果所有符合长度要求的Bucket都是“空”的,那么我们会根据指定长度对应Bucket创建一个字节数组(长度为该Bucket对应长度区间的最大值),并封装成返回的ByteArrayOwner 对象。上面介绍的针对POH的分配体现在针对GC.AllocateUninitializedArray<byte>方法的调用,我们将pinned参数设置为True。

四、测试

ByteArrayPool针对字节数组的池化通过如下的程序来演示。

  1. var pool = ByteArrayPool.Create(maxLength: 1000);
  2. var length = 100;
  3. var minLength = 65;
  4. var maxLength = 128;
  5.  
  6. // 在允许的最大长度内,被池化
  7. var owner = pool.Rent(minimumLength: length);
  8. Debug.Assert(owner.IsPooled);
  9. var bytes = owner.Bytes;
  10. Debug.Assert(bytes.Length == maxLength);
  11. owner.Dispose();
  12. for (int len = minLength; len <= maxLength; len++)
  13. {
  14. using (owner = pool.Rent(len))
  15. {
  16. Debug.Assert(owner.IsPooled);
  17. Debug.Assert(ReferenceEquals(owner.Bytes, bytes));
  18. }
  19. }
  20.  
  21. // 只有被释放的数组才会被复用
  22. owner = pool.Rent(minimumLength: length);
  23. Debug.Assert(!ReferenceEquals(owner.Bytes, pool.Rent(minimumLength: length).Bytes));
  24.  
  25. // 超出最大长度,不会被池化
  26. owner = pool.Rent(minimumLength: pool.MaxArrayLength + 1);
  27. Debug.Assert(!owner.IsPooled);
  28. bytes = owner.Bytes;
  29. owner.Dispose();
  30. Debug.Assert(!ReferenceEquals (pool.Rent(minimumLength: pool.MaxArrayLength + 1).Bytes, bytes));

原文链接:https://www.cnblogs.com/artech/p/byte-array-pool.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号