一:背景
1. 讲故事
最近在分析一个 dump 的过程中发现其在 gen2 和 LOH 上有不少size较大的free,仔细看了下,这些free生前大多都是模板引擎生成的html片段的byte[]数组,当然这篇我不是来分析dump的,而是来聊一下,当托管堆有很多length较大的 byte[] 数组时,如何让内存利用更高效,如何让gc老先生压力更小。
不知道大家有没有发现在 .netcore 中增加了不少池化对象的东西,比如:ArrayPool,ObjectPool 等等,确实在某些场景下还是特别实用的,所以有必要对其进行较深入的理解。
二:ArrayPool 源码分析
1. 一图胜千言
在我花了将近一个小时的源码阅读之后,我画了一张 ArrayPool 的池化图,所谓:一图在手,天下我有 。
有了这张图,接下来再聊几个概念并配上相应源码,我觉得应该就差不多了。
2. 池化的架构分级是什么样的?
ArrayPool 是由若干个 Bucket 组成, 而 Bucket 又由若干个 buffer[] 数组组成, 有了这个概念之后,再配一下代码。
public abstract class ArrayPool
{
public static ArrayPool Create()
{
return new ConfigurableArrayPool();
}
}
internal sealed class ConfigurableArrayPool : ArrayPool
{
private sealed class Bucket
{
internal readonly int _bufferLength;
private readonly T[][] _buffers;
private int _index;
}
private readonly Bucket[] _buckets; //bucket数组
}
3. 为什么每一个 bucket 里都有 50 个 buffer[]
这个问题很好回答,初始化时做了 maxArraysPerBucket=50 设定,当然你也可以自定义,具体参考如下代码:
internal sealed class ConfigurableArrayPool : ArrayPool
{
internal ConfigurableArrayPool() : this(1048576, 50)
{
}
internal ConfigurableArrayPool(int maxArrayLength, int maxArraysPerBucket)
{
int num = Utilities.SelectBucketIndex(maxArrayLength);
Bucket[] array = new Bucket[num + 1];
for (int i = 0; i < array.Length; i++)
{
array[i] = new Bucket(Utilities.GetMaxSizeForBucket(i), maxArraysPerBucket, id);
}
_buckets = array;
}
}
4. bucket 中 buffer[].length 为什么依次是 16,32,64 ...
框架做了默认假定,第一个bucket中的 buffer[].length=16, 后续 bucket 中的 buffer[].length 都是 x2 累计,涉及到代码就是 GetMaxSizeForBucket() 方法,参考如下:
internal ConfigurableArrayPool(int maxArrayLength, int maxArraysPerBucket)
{
Bucket[] array = new Bucket[num + 1];
for (int i = 0; i < array.Length; i++)
{
array[i] = new Bucket(Utilities.GetMaxSizeForBucket(i), maxArraysPerBucket, id);
}
}
internal static int GetMaxSizeForBucket(int binIndex)
{
return 16