在C#应用程序开发过程中,当我们需要频繁使用临时数组时,每次都新建数组会导致大量短生命周期对象产生,触发更频繁的垃圾回收,进而影响程序性能。ArrayPool是.NET框架内置的数组对象池实现,通过复用已分配的数组对象,能够有效减少内存分配次数,降低GC压力,是性能优化的重要手段。

ArrayPool核心原理
ArrayPool的本质是一个数组对象的缓存池,它会预先分配一定数量的数组对象并保存起来,当开发者需要数组时,优先从池中获取已存在的数组,使用完成后将数组归还到池中,供后续其他逻辑复用。这样就避免了反复向堆申请内存和触发垃圾回收的过程。
ArrayPool是线程安全的,支持多线程环境下的并发获取和归还操作,内部会根据数组的长度和类型进行分类管理,保证不同规格的数组不会互相干扰。
ArrayPool基础使用方式
获取ArrayPool实例
ArrayPool提供了两种获取实例的方式,分别是共享池和自定义池:
- 共享池:通过
ArrayPool<T>.Shared获取,是全局共享的实例,适合大多数通用场景,不需要手动管理生命周期。 - 自定义池:通过
ArrayPool<T>.Create()创建,可以指定池的最大数组长度和最大池化数量,适合有特殊需求的场景。
数组的租借与归还
使用ArrayPool租借数组需要调用Rent方法,使用完成后必须调用Return方法归还,否则会导致池中的数组无法复用,失去优化的意义。
下面是基础的使用示例:
using System;
using System.Buffers;
class Program
{
static void Main()
{
// 获取int类型的共享ArrayPool实例
ArrayPool<int> pool = ArrayPool<int>.Shared;
// 租借一个长度至少为10的int数组,实际返回的数组长度可能大于等于10
int[] rentedArray = pool.Rent(10);
try
{
// 使用数组,这里模拟给数组赋值
for (int i = 0; i < 10; i++)
{
rentedArray[i] = i;
}
// 处理数组逻辑,比如打印内容
Console.WriteLine($"数组第一个元素:{rentedArray[0]}");
}
finally
{
// 归还数组到池中,第二个参数表示是否清空数组内容,默认false不清空
pool.Return(rentedArray, clearArray: true);
}
}
}
使用ArrayPool的注意事项
数组长度问题
Rent方法的参数是最小所需长度,实际返回的数组长度可能大于请求的长度,因此在使用数组时,不要依赖返回数组的长度,而是要使用自己请求的长度或者记录实际使用的长度,避免访问超出预期范围的索引。
归还规则
- 归还的数组必须是之前从该池中租借的,不能把普通新建的数组归还到池中。
- 同一数组不能重复归还,否则会导致池状态异常。
- 如果租借的数组在使用过程中被修改了长度(比如重新赋值了新的数组),也不能归还到池中。
清空选项
Return方法的clearArray参数需要谨慎选择:如果数组中包含敏感数据或者后续复用该数组的逻辑不保证会覆盖所有元素,建议设置为true,清空数组内容;如果后续使用时会完全覆盖数组的所有元素,可以设置为false,减少不必要的性能开销。
性能对比测试
我们可以通过一个简单的测试对比使用ArrayPool和不使用ArrayPool的性能差异,测试场景是高频创建长度为1000的int数组:
using System;
using System.Buffers;
using System.Diagnostics;
class PerformanceTest
{
static void TestWithoutPool()
{
for (int i = 0; i < 100000; i++)
{
int[] array = new int[1000];
// 模拟数组使用
array[0] = i;
}
}
static void TestWithPool()
{
ArrayPool<int> pool = ArrayPool<int>.Shared;
for (int i = 0; i < 100000; i++)
{
int[] array = pool.Rent(1000);
try
{
array[0] = i;
}
finally
{
pool.Return(array, false);
}
}
}
static void Main()
{
Stopwatch sw1 = Stopwatch.StartNew();
TestWithoutPool();
sw1.Stop();
Console.WriteLine($"不使用ArrayPool耗时:{sw1.ElapsedMilliseconds}ms");
Stopwatch sw2 = Stopwatch.StartNew();
TestWithPool();
sw2.Stop();
Console.WriteLine($"使用ArrayPool耗时:{sw2.ElapsedMilliseconds}ms");
}
}
实际运行后可以看到,使用ArrayPool的场景耗时明显更低,且过程中GC的触发次数会大幅减少,尤其是在循环次数更多、数组更大的场景下,优化效果会更显著。
适用场景与不适用场景
适用场景
- 高频临时数组的使用场景,比如网络数据接收、文件读取、数据序列化反序列化等。
- 数组长度较大,创建和回收成本较高的场景。
- 对性能要求较高的服务端应用、游戏逻辑等。
不适用场景
- 数组使用频率很低,几乎不会重复创建的场景,使用ArrayPool反而会增加额外的管理开销。
- 需要长期持有数组的场景,因为租借的数组应该尽快归还,长期持有会占用池的资源,影响其他逻辑的复用。
- 数组长度非常小(比如长度小于16),创建成本极低,使用ArrayPool的收益不明显。
自定义ArrayPool配置
如果需要自定义ArrayPool的参数,可以使用ArrayPool<T>.Create方法,该方法有两个重载:
Create():创建默认配置的池,最大数组长度为1024*1024,每个桶的最大数组数量为50。Create(int maxArrayLength, int maxArraysPerBucket):指定最大支持的数组长度和每个长度桶的最大数组数量。
示例代码如下:
using System;
using System.Buffers;
class CustomPoolExample
{
static void Main()
{
// 创建自定义池,最大支持长度为2048的数组,每个长度桶最多存20个数组
ArrayPool<byte> customPool = ArrayPool<byte>.Create(2048, 20);
byte[] data = customPool.Rent(1024);
try
{
// 使用数组
data[0] = 0x01;
}
finally
{
customPool.Return(data);
}
}
}
通过合理配置自定义池的参数,可以更好地适配业务场景,避免池占用过多内存或者无法满足需求的情况。
C#_ArrayPool对象池GC优化性能优化修改时间:2026-07-06 23:00:56