ArrayPool 源码解读之 byte[] 也能池化?

一:背景

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<T>
{public static ArrayPool<T> Create(){return new ConfigurableArrayPool<T>();}
}internal sealed class ConfigurableArrayPool<T> : ArrayPool<T>
{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<T> : ArrayPool<T>
{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 << binIndex;
}

5. 初始化时 bucket 到底有多少个?

其实在上图中我也没有给出 bucket 到底有多少个,那到底是多少个呢????????????? ,当我阅读完源码之后,这算法还挺有意思的。

先说一下结果吧,默认 17 个 bucket,你肯定会好奇怎么算的?先说下两个变量:

  • maxArrayLength=1048576 = 2的20次方

  • buffer.length= 16 = 2的4次方

最后的算法就是取次方的差值:bucket[].length= 20 - 4 + 1 = 17,换句话说最后一个 bucket 下的 buffer[].length=1048576,详细代码请参考 SelectBucketIndex() 方法。


internal sealed class ConfigurableArrayPool<T> : ArrayPool<T>
{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;}internal static int SelectBucketIndex(int bufferSize){return BitOperations.Log2((uint)(bufferSize - 1) | 0xFu) - 3;}
}

到这里我相信你对 ArrayPool 的池化架构思路已经搞明白了,接下来看下如何申请和归还 buffer[]。

三:如何申请和归还

既然 buffer[] 做了颗粒化,那就应该好借好还,反应到代码上就是 Rent()Return() 方法,为了方便理解,上代码说话:

class Program{static void Main(string[] args){var arrayPool = ArrayPool<int>.Create();var bytes = arrayPool.Rent(10);for (int i = 0; i < bytes.Length; i++) bytes[i] = 10;arrayPool.Return(bytes);Console.ReadLine();}}

有了代码和图之后,再稍微捋一下流程。

  1. 从 ArrayPool 中借一个 byte[10] 大小的数组,为了节省内存,先不备货,临时生成一个 byte[].size=16 的数组出来,简化后的代码如下,参考 if (flag) 处:

internal T[] Rent(){T[][] buffers = _buffers;T[] array = null;bool lockTaken = false;bool flag = false;try{if (_index < buffers.Length){array = buffers[_index];buffers[_index++] = null;flag = array == null;}}if (flag){array = new T[_bufferLength];}return array;}

这里有一个坑,那就是你以为借了 byte[10],现实给你的是 byte[16],这里稍微注意一下。

  1. 当用 ArrayPool.Return 归还 byte[16] 时, 很明显看到它落到了第一个bucket的第一个buffer[]上,参考如下简化后的代码:

internal void Return(T[] array){if (_index != 0){_buffers[--_index] = array;}}

这里也有一个值得注意的坑,那就是还回去的 byte[16] 里面的数据默认是不会清掉的,从上面的代码也是可以看出来的,要想做清理,需要在 Return 方法中指定 clearArray=true,参考如下代码:

public override void Return(T[] array, bool clearArray = false){int num = Utilities.SelectBucketIndex(array.Length);if (num < _buckets.Length){if (clearArray){Array.Clear(array, 0, array.Length);}_buckets[num].Return(array);}}

四:总结

学习这其中的 池化架构 思想,对平时项目开发还是能提供一些灵感的,其次对那些一次性使用 byte[] 的场景,用池化是个非常不错的方法,这也是我对朋友dump分析后提出的一个优化思路。

END

工作中的你,是否已遇到 ... 

1. CPU爆高

2. 内存暴涨

3. 资源泄漏

4. 崩溃死锁

5. 程序呆滞

等紧急事件,全公司都指望着你能解决...  危难时刻才能展现你的技术价值,作为专注于.NET高级调试的技术博主,欢迎微信搜索: 一线码农聊技术,免费协助你分析Dump文件,希望我能将你的踩坑经验分享给更多的人。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/298792.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

本地环境和测试环境搭建

1.wampserver&#xff08;可兼容后端php&#xff09; 2.tomcate 3.edp webserver&#xff08;百度内部&#xff09; 4.fiddle重定向&#xff08;可以把在线的网页直接接入本地的代码进行替换看效果&#xff09;转载于:https://www.cnblogs.com/cjy1993/p/4097123.html

为什么有些人从不点开朋友圈?

全世界只有3.14 % 的人关注了爆炸吧知识真正决定人与人之间的差距的&#xff0c;其实是我们对事物的见识与内心的格局&#xff0c;见识的深浅决定人生的深浅&#xff0c;格局的大小决定了人生之路是宽是窄。今天给大家推荐几个有深度、有想法的公众号&#xff0c;希望能够给你带…

wxPython:登录工具

最近一直在学习Python的基础和一些常用的模块&#xff0c;现在该是付诸实践的时候了。 我打算做的第一个小工具是利用wxPython来创建一个登录小工具&#xff0c;这主要是减轻自己日常工作中的一些负担。具体需求是这样的&#xff0c;在出现工具的UI之后&#xff0c;用户可以选择…

微信 小程序 python 渲染_微信小程序渲染html内容

最近又做了一个新的小程序关于物流订单查询欢迎来体验遇到了一个小问题&#xff1a;数据中返回电话号码的字符串识别出来并且高亮和可以绑定事件。比如数据中包含您的派送员黄xx正在派件&#xff0c;电话&#xff1a;137xxxx41460已经在派送。其中就要识别出137xxxx41460并且绑…

淘宝一淘网收录部分垂直B2C网站信息

12月23日下午消息&#xff0c;淘宝旗下一淘网搜索近日悄然收录当当、红孩子等垂直B2C网站的折扣信息&#xff0c;网友可轻松在外部垂直B2C网站和淘宝站内商家间做出对比和选择。 之前有消息称&#xff0c;淘宝网已经通过站内搜索中的“导购”功能已经实现了与一淘的互通&#…

shell oracle查询数组,shell 脚本 ---数组

数组的定义&#xff1a;所谓数组&#xff0c;就是相同数据类型的元素按一定顺序的集合&#xff0c;就是把有限个类型相同的变量用一个名字命令&#xff0c;也就是说这些变量被定义成数组之后&#xff0c;它们就不在有自己的名字了&#xff0c;那么我们怎么找到各个变量或者元素…

Dockerfile 使用 ARG 参数实现构建模板

Dockerfile 使用 ARG 参数实现构建模板IntroDockerfile 里用来表示变量的主要有两个东西&#xff0c;一个是 ENV 代表了环境变量&#xff0c;另外一个则是 ARG 代表是构建 docker 镜像时的一个构建参数&#xff0c;需要在执行 docker build 命令时指定变量的值&#xff0c;最近…

关于更换液晶屏(LCD)后“输入不支援”的一种解决方案

今天新装了一台监控主机&#xff0c;一切调试完毕之后就装箱了。没想到拿到客户那里之后却出现了一个小小的问题&#xff0c;XP进度条跳过之后显示器出现“输入不支援”&#xff0c;反复重启之后仍然无法解决&#xff0c;自己在公司刚刚调试好了的呀&#xff0c;哦想起来了&…

python 数据驱动接口自动化框架_python接口自动化测试 - 数据驱动DDT模块的简单使用...

DDT简单介绍 名称&#xff1a;Data-Driven Tests&#xff0c;数据驱动测试 作用&#xff1a;由外部数据集合来驱动测试用例的执行 核心的思想&#xff1a;数据和测试代码分离 应用场景&#xff1a;一组外部数据来执行相同的操作 优点&#xff1a;当测试数据发生大量变化的情况下…

【知识分享】异步调用与多线程的区别

随着拥有多个硬线程CPU&#xff08;超线程、双核&#xff09;的普及&#xff0c;多线程和异步操作等并发程序设计方法也受到了更多的关注和讨论。本文主要是想探讨一下如何使用并发来最大化程序的性能。 多线程和异步操作的异同 多线程和异步操作两者都可以达到避免调用线程阻塞…

华人AI界痛失“一代宗师”,计算机视觉之父黄煦涛教授去世

全世界只有3.14 % 的人关注了爆炸吧知识美东时间2020年4月25日夜间&#xff0c;华人计算机视觉一代宗师&#xff0c;黄煦涛教授&#xff08;Thomas S. Huang&#xff09;在美国印第安纳州逝世&#xff0c;享年 84 岁。由于他在图像处理、模式识别等计算机视觉领域作出的开创性贡…

oracle 参照完整性,Oracle中用表外键来保证系统参照完整性

欢迎进入Oracle社区论坛&#xff0c;与200万技术人员互动交流 >>进入 Oracle中表的外键是保证系统参照完整性的手段&#xff0c;而参照完整性是指分布在两个表中的列所满足的具有主从性质的约束关系。外键涉及到两个表&#xff0c;其中一个称之为父项表&#xff0c;另一个…

关于数据仓库 — 总体工具介绍

数据仓库项目是以关系数据库为依托&#xff0c;以数据仓库理论为指导、以 OLAP为多层次多视角分析&#xff0c;以 ETL工具进行数据集成、整合、清洗、加载转换&#xff0c;以前端工具进行前端报表展现浏览&#xff0c;以反复叠代验证为生命周期的综合处理过程。最终目标是为了达…

你好,同学!在云端学习最潮的技术吧!

开学季大礼包9月开学了&#xff0c;作为学生的你&#xff0c;有想过在这个数字化年代&#xff0c;学最cool的技术吗&#xff1f;人工智能&#xff0c;物联网&#xff0c;云计算&#xff0c;还有区块链这些互联网产物影响着你的生活&#xff0c;也影响着你将来的职业发展。不论你…

seo关键词互点软件报价_SEO关键词优化收费问题和外包报价问题,一文详解

在竞争日益激烈的市场环境中&#xff0c;企业为了在互联网平台中获得较好的排名&#xff0c;以及实现产品的较好变现&#xff0c;大多数都是使用SEO搜索引擎&#xff0c;因为通过优化关键词可以不断地为用户创造“用户最想得到的”“最匹配”搜索结果&#xff0c;在快速找到心仪…

15张令人震撼的物理动图,看完惊呆了!

全世界只有3.14 % 的人关注了爆炸吧知识比抖音还上瘾看了会让人上瘾的物理动图&#xff0c;赶紧给家里的孩子看看吧&#xff0c;绝对让他开拓眼界&#xff0c;脑洞大开。1.有弹性的岩浆2.高速转动时&#xff0c;因向心力不足而被撕开的的CD&#xff08;慢镜头&#xff09;3.震荡…

linux目录结果说明,Linux目录结构及文件说明

Linux中所有文件都是从(/)根开始的&#xff0c;下面是典型的Linux目录结构说明&#xff1a;/&#xff1a;根目录/bin&#xff1a; binary 主要用来存放可执行文件/sbin&#xff1a; super bin 存放系统管理程序&#xff0c;通常只有管理员才有权限使用/boot&#xff1a; 存放内…

Java wait notify

2019独角兽企业重金招聘Python工程师标准>>> Java wait && notify ‍wait、notify和notifyAll方法是Object类的final native方法&#xff0c;所以这些方法不能被子类重写。 方法 notifyAll() Wakes up all threads that are waiting on this objects monito…

使用ETag协议实现ASP.NET Core API缓存

通常&#xff0c;我们在ASP.NET Core API服务端实现缓存&#xff0c;数据直接从缓存中取出&#xff0c;返回给客户端&#xff0c;以便加快响应速度。但是这样的做法&#xff0c;解决不了数据传输到客户端需要占用带宽带来的性能问题。这时&#xff0c;可以尝试使用ETag。ETag协…