时间:2022-05-13 13:51:20 | 栏目:.NET代码 | 点击:次
Create / Shared var pool=ArrayPool[byte].Shared
Rent()
函数,租用缓冲区空间 byte[] array=pool.Rent(1024)
Return(array[T])
函数,归还租用的空间 pool.Return(array)
Shared
返回为一个静态共享实例,实际返回了一个 TlsOverPerCoreLockedStacksArrayPool
internal sealed class TlsOverPerCoreLockedStacksArrayPool<T> : ArrayPool<T> { private static readonly TlsOverPerCoreLockedStacksArrayPool<T> s_shared = new TlsOverPerCoreLockedStacksArrayPool<T>(); public static ArrayPool<T> Shared => s_shared; }
租用数组长度不可超过 2^20( 1024*1024 = 1 048 576),
否则会从GC中重新开辟内存空间
Rent
租用数组实际返回的长度可能比请求的长度大,返回长度一是(16*2^n)
归还缓冲区的时候,如果不设置
Return clearArray
,下一个租用者可能会看到之前的填充的值(在返回的数组长度刚好是下一个租用者请求的长度时会被看到)
缓冲池的内存释放不是实时释放,在缓冲区空闲时,大概10到20秒之后,会随着第2代GC一起释放,分批释放
并发数量持续增长时,缓冲池占用的内存空间也会持续增长,而且似乎没有上限
private static void TimeMonitor() { //随机生成3000个数组的长度值 var sizes = new int[30000]; Parallel.For(0, 10000, x => { sizes[x] = new Random().Next(1024 * 800, 1024 * 1024); }); //缓冲池方式租用数组 var gcAllocate0 = GC.GetTotalAllocatedBytes(); var watch = new Stopwatch(); Console.WriteLine("start"); watch.Start(); for (int i = 0; i < 10000; i++) { //CreateArrayByPool(ArrayPool<int>.Shared, 1024 * 1024,sizes[i], false); var arr = ArrayPool<int>.Shared.Rent(sizes[i]); for (int j = 0; j < sizes[i]; j++) { arr[j] = i; } ArrayPool<int>.Shared.Return(arr, true); } var time1 = watch.ElapsedMilliseconds; var gcAllocate1 = GC.GetTotalAllocatedBytes(true); //new 方式分配数组空间 watch.Restart(); for (int i = 0; i < 30000; i++) { //CreateArrayDefault(i, sizes[i], false); var arr = new int[sizes[i]]; for (int j = 0; j < sizes[i]; j++) { arr[j] = i; } } var time2 = watch.ElapsedMilliseconds; var gcAllocate2 = GC.GetTotalAllocatedBytes(true); Console.WriteLine("ArrayPool方式创建数组耗时:" + time1 + " Gc总分配量" + (gcAllocate1 - gcAllocate0)); Console.WriteLine("默认方式创建数组耗时:" + time2 + " Gc总分配量" + (gcAllocate2 - gcAllocate1 - gcAllocate0)); }
内存使用截图:左侧没有波动的横线是缓冲池执行的过程,右侧为手动创建数组的执行过程
执行结果:
ArrayPool方式创建数组耗时:17545 Gc总分配量4130800
默认方式创建数组耗时:26870 Gc总分配量37354100896
private static void PostFileByBytesPool(FormFile file) { HttpClient client = new HttpClient() { BaseAddress = new Uri("https://fileserver.com") }; var fileLen = (int)file.Length; var fileArr = ArrayPool<byte>.Shared.Rent(fileLen); using var stream = file.OpenReadStream(); stream.Read(fileArr, 0, fileLen); MultipartFormDataContent content = new MultipartFormDataContent(); content.Add(new ByteArrayContent(fileArr, 0, fileLen), "id_" + Guid.NewGuid().ToString(), file.FileName); client.PostAsync("/myfile/" + file.FileName, content).Wait(); ArrayPool<byte>.Shared.Return(fileArr, true); }
ArrayPool
的Create()
函数会创建一个 ConfigurableArrayPool
对象
ConfigurableArrayPool
的构造函数接收两个参数
1024*1024*1024
通过这两个参数可以解决 Shared
方式的两个问题:
示例:
//创建一个自定义缓冲池实例,单个数组最大长度为1024 * 2048,最大可同时租用10个缓冲区 ArrayPool<int> CustomerArrayPool = ArrayPool<int>.Create(1024 * 2048,10);
与Shared不同的是,如果设置 CustomerArrayPool=Null
那么在下一次垃圾回收时该缓冲池所占的内存会立马全部释放。
为防止不可预测的风险,应该保持CustomerArrayPool
的存活。
同时为了防止内存的滥用应该限制CustomerArrayPool
的数量