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,一经查实,立即删除!

相关文章

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

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

wxPython:登录工具

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

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

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

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

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

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

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

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

开学季大礼包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协…

深度优化sql 查询, 提升性能一百倍是什么概念?

正在做一个软件设计, 希望有个功能, 然而, 对于加上该功能后对系统性能造成的影响很是担忧. 可以说是, 一方面想要有这个功能, 另一方面又对性能问题是否能够解决很怀疑, 正处于犹豫不决状态. 于是决定进行实验. 首先对表结构和索引进行了优化, 初步结果还不错, 性能基本进入可…

女老师vs男老师的区别...

1 被帅到了2 失传已久的如来神掌&#xff1f;&#xff1f;&#xff01;&#xff01;3 哎呦&#xff0c;谁拉我一把&#xff1f;4 决定到底要不要开始学习的我…5 女老师vs男老师监考的区别...6 函数广播体操7 这个打包装置够便捷&#xff0c;够酷炫&#xff01;关键是省事你点的…

Redis Windows环境安装

1、下载Windows 版本 Redis: https://github.com/ServiceStack/redis-windows 2、 解压文件&#xff1a; F:\开源代码学习\01_Redis 打开 目录&#xff1a;F:\开源代码学习\01_Redis\src\msopentech\redis64-2.6.12.1 3、启动Redis 指向CMD命令&#xff1a; 4、测试安装成果&am…

Hello Blazor:(11)全局截获事件执行

前言在Blazor中&#xff0c;我们使用on{DOM EVENT}"{DELEGATE}"这样的Razor语法在组件标记中指定委托事件处理程序&#xff1a;<button onclick"IncrementCount">Click me</button>但是没有提供解除委托的方法。比如&#xff0c;我们需要在某种…

深度学习会不会被取代?深度学习必看发展史

近年来&#xff0c;随着人工智能时代的来临&#xff0c;数据科学、计算机科学迎来飞速发展&#xff0c;多次引发讨论的人机对战也正是人工智能与人类的对决。从无人驾驶汽车到AlphaGo战胜人类&#xff0c;机器学习成为了当下最热门的技术。而机器学习中一种重要的方法就是深度学…

Xcode6中如何对scrollview进行自动布局(autolayout)

本文转载至 http://www.cocoachina.com/ios/20141011/9871.html XCodeAutolayoutscrollView Xcode6中极大的增强了IB中自动布局的能力&#xff0c;下面就通过对刺儿头scrollview进行一次自动布局实战&#xff0c;看看自动布局在Xcode6中到底值不值得使用。 说 UIScrollView是个…

linux文件读保护,Linux Rootkit实现文件保护

一个非常基础的rootkit&#xff0c;禁止读取指定文件编译系统:CentOS 7uname -r3.10.0-957.21.3-el7.x86_64#include #include #include asmlinkage long(*real_open)(const char __user *filename, int flags, unsigned short mode);unsigned long **syscall_table NULL;char…