ArrayPool.Shared解说
ArrayPool简介
.NET 中频繁创建和销毁数组的情况下会导致垃圾回收器出现严重的内存压力,ArrayPool<T> 通过池化手段有效地降低了数组的分配和垃圾回收器的回收压力,同时 ArrayPool<T> 也是 MemoryPool<T> 和 PipeWriter、PipeReader 的底板。
ArrayPool<T>.Shared 是 ArrayPool<T> 的一种实现,它设计成静态共享以供全体共同使用。在实际应用中, Shared 实例几乎承载了全部的 ArrayPool<T> 调用。
Shared实例的Rent
static void RentAndReturn(int size)
{
var pool = ArrayPool<byte>.Shared;
var array = pool.Rent(size);
// 在这里使用 array
pool.Return(array);
}
其中输入的 size 和 Rent 得到 array.Length的关系如下:
var index = SelectBucketIndex(size);
var arrayLenth = GetMaxSizeForBucket(index);
static int SelectBucketIndex(int size)
{
return BitOperations. Log2((uint)((size - 1) | 0xF)) - 3;
}
static int GetMaxSizeForBucket(int bucketIndex)
{
return 16 << bucketIndex;
}
调用 SelectBucketIndex(int.MaxValue) 会得到27,实际上 Shared 实现只维护最大索引值为 26 的总共 27 个 Bucket,所以当你 Rent 出大于 1GB 的数组时永远触发分配且不会 Return 到 Bucket 里。
Shared实例的Return
如果我们书写如下代码,会发生什么情况?
var pool = ArrayPool<byte>.Shared;
var array1 = new byte[16];
pool.Return(array1);
var array2 = new byte[16];
pool.Return(array2);
array1 和 array2 都不是 Rent 出来的,Return 操作 pool 会接受吗?
答案是都接受,只要 array.Length 符合要求都会接受,而且也不会考虑多次 Return 同一个引用 array 的特殊情况。所以千万不要对同一个 Rent 到的 array 实例进行多次 return 操作。
Shared实例的Thread-Local-Storage
如果我们书写如下代码,会触发 Shared 的 Buckets 读写访问吗?
var pool = ArrayPool<byte>.Shared;
for(var i =0; i <100; i++)
{
var array = pool.Rent(1024 * 1024);
pool.Return(array);
}
答案是不会触发 Buckets 读写访问,执行之后 27 个 Bucket 还是未初始化的 null 值,因为Shared使用Thread-Local-Storage做了第一层无锁缓存。
原始代码
[ThreadStatic]
private static SharedArrayPoolThreadLocalArray[]? t_tlsBuckets;
方便理解的简化代码
// 创建一个 Array 类型的数组,数组共27个元素
// 每个元素的索引值,对应 Bucket 的索引
[ThreadStatic]
private static Array? [] t_tlsBuckets = new Array[27];
当执行
var array = ArrayPool<byte>.Shared.Rent(16);
实际的逻辑是
var index = SelectBucketIndex(16); // index = 0;
var array = t_tlsBuckets[index];
if(array != null)
{
t_tlsBuckets[index] = null; // 当然这里不会对t_tlsBuckets进行2次索引访问,使用 ref 来解决
return array;
}
Return 的时候也是一样的缓存访问逻辑,当 t_tlsBuckets 索引对应的元素为 null 时缓存成功。
Shared实例的Buckets
当 t_tlsBuckets[index] 缓存不命中时,会触发 Bucket = Buckets[index] 的创建和访问。注意这里一个 Bucket 不是 Array的集合,而是Array的集合的集合,我们叫他Partitions。为了方便理解,下面给出示意代码:
// bucket 是 Partition 的集合,Partition 是 Array 的集合
// bucket 的元素数量是 Environment.ProcessorCount
List<Partition> bucket = Buckets[index];
Debug.Assert(bucket.Count == Environment.ProcessorCount);
当进行 Rent 操作时,尝试从 bucket 里弹出一个 Array,其示意过程如下(注意真实的实现为高性能代码可理解性更难):
// bucket 是 Partition 的集合,Partition 是 Array 的集合
// bucket 的元素数量是 Environment.ProcessorCount
List<Partition> bucket = Buckets[index];
// 每个 cpu 核心操作固定的一个 Partition,目的是去锁化
int cpuIndex = Thread.GetCurrentProcessorId() % Environment.ProcessorCount;
Partition cpuPartition = bucket[cpuIndex];
// Partition 的元素数量固定是 32
Debug.Assert(cpuPartition.Count == 32);
// 尝试从当前 cpu 核心对应的 Partition 进行TryPop()
// TryPop() 的实现有排他锁,但这里只要没有别的线程来访问此 Partition,就不会产生互斥
// 如果在同一线程下租赁过32次且不归还,TryPop()会失败
Array array = null;
if ((array = cpuPartition.TryPop()) != null)
{
return array;
})
// 以上失败后则尝试从其它的 Partition TryPop(),此时排他锁可能就生效了
for(var index = 0; index < bucket.Count; index++)
{
if(index!= cpuIndex)
{
Partition otherPartition = bucket[index];
if ((array = otherPartition.TryPop()) != null)
{
return array;
})
}
}
return null;
在进行 Return 操作是,大的流程和 Rent 一样,不同是 TryPop() 变成了TryPush()。
总结一下,在指定 size 之后,Shared 实例最多能 Rent(size) 出 Environment.ProcessorCount * 32 个可复用的数组,伴随着 Return 的及时性越低,Rent 时排它锁触发的几率就越高。
总结
以为是本人对 ArrayPool.Shared 实现的分析和理解,并变换成一种相对容易理解的伪代码,如有不对之处请指正。
ArrayPool.Shared解说的更多相关文章
- 使用ArrayPool池化大型数组(翻译)
原文链接:https://adamsitnik.com/Array-Pool/ 使用ArrayPool 简介 .NET的垃圾收集器(GC)实现了许多性能优化,其中之一就是,设定年轻的对象很快消亡,然而 ...
- 如何使用 ArrayPool
如果不停的 new 数组,可能会造成 GC 的压力,因此在 aspnetcore 中推荐使用 ArrayPool 来重用数组,本文将介绍如何使用 ArrayPool. 使用 ArrayPool Arr ...
- NET高性能IO
System.IO.Pipelines: .NET高性能IO https://www.cnblogs.com/xxfy1/p/9290235.html System.IO.Pipelines是一个新的 ...
- .Net Core后端架构实战【2-实现动态路由与Dynamic API】
摘要:基于.NET Core 7.0WebApi后端架构实战[2-实现动态路由与Dynamic API] 2023/02/22, ASP.NET Core 7.0, VS2022 引言 使用过ABP ...
- .NET周报 【4月第2期 2023-04-08】
国内文章 LRU缓存替换策略及C#实现 https://www.cnblogs.com/eventhorizon/p/17290125.html 这篇文章讲述了缓存替换策略,特别是LRU算法.LRU算 ...
- 【开源】OSharp框架解说系列(2.2):EasyUI复杂布局及数据操作
OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...
- 【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构
OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...
- tomcat简介及原理解说
Tomcat简介 作者:杨晓(http://blog.sina.com.cn/u/1237288325) 目录: ----Tomcat背景 ----Tomcat目录 ----Tomcat类加载 --- ...
- ASP.NET MVC流程解说
开始想这个标题时,,很忧郁什么标题将得到更好的叫什么,最后确定的解释,虽然稍0基金会,但是,这个概念是非常.我想出了一个相当的价格值的. ,開始. 1.MVC的基本开发流程 2.webform和M ...
- iOS单例设计模式具体解说(单例设计模式不断完好的过程)
在iOS中有非常多的设计模式,有一本书<Elements of Reusable Object-Oriented Software>(中文名字为<设计模式>)讲述了23种软件设 ...
随机推荐
- 简单dp 二维
转载问题 http://www.cnblogs.com/moqitianliang/p/4798882.html 简要 概括动态规划 就是求原子问题,把一个问题分解成一个原子问题. 二维数组的简单 ...
- linux 安装obs 报错 xcb
简介 如何解决这个问题呢? 首先开启 qtdebug export QT_DEBUG_PLUGINS=1 然后发现是 找不库 libqxcb.so obs 会去一个目录下寻找他, 然后我们创建这个目录 ...
- 如何通过iPaaS对数据作预警监控
通过iPaaS的企业级API网关的预警设置功能即可实现监控,预警设置功能可配置多种预警机制,如API超时发送通知.调用错误发送通知.速率异常通知.业务数据异常通知等多场景监测预警.也可使用邮件.企业微 ...
- [题解] AT_ABC409_D String Rotation
题目传送门 题目 您将得到一个长度为 \(N\) 的字符串 \(S=S_1 S_2\dots S_n\) ,该字符串由小写英文字母组成. 您将在 \(S\) 上仅执行一次以下操作:选择长度至少为 \( ...
- 进阶篇:3.2.1)DFM钣金-冲压件设计
本章目的:设计符合钣金制造工艺的零件,不再犯简单错误,不必再为反复修改模具而烦恼. 1.基础阅读 进阶篇:2)DFMA的介绍 进阶篇:2.3)DFMA的运用方法(个人方法) 2.钣金冲压(Stampi ...
- 深度消息:微软Win11系统会有LTSC版本
据深度之家的最新信息:微软官方在AMA(Ask Me Anything:问我任何问题)确认,win11系统和Win10系统一样,提供每月安全更新.可选累积更新.服务堆栈更新等服务. 在AMA上微软官方 ...
- Django实时通信实战:WebSocket与ASGI全解析(下)
一.实战:构建实时聊天室 环境准备 下面将使用 Django Channels 构建一个多用户实时聊天室.Django Channels的介绍.安装与配置,参考上篇. 实现 WebSocket 消费者 ...
- Xshell 上传下载本地文件
1.需要先安装lrzsz yum -y install lrzsz apt install lrzsz ( 查看 apt list --installed) 未安装找不到rz命令 2.检验安装结果 r ...
- 定位与专长的分野:ThingsBoard 物联网平台与 MyEMS 能源管理系统的深度对比
在开源技术生态中,ThingsBoard 与 MyEMS 分别在物联网(IoT)全域管理与能源垂直领域展现出独特价值.两者虽同属数据驱动的技术平台,但在核心定位.功能设计与应用场景上呈现显著差异,反映 ...
- Java核心类——3.StringJoiner
目录 Java中的StringJoiner StringJoiner的基本用法 基本构造与使用 带前缀和后缀的拼接 String.join():简化的拼接方法 StringJoiner其他使用 合并两 ...