本来想写篇关于System.Collections.Immutable中提供的ImmutableList里一些实现细节来着,结果一时想不起来源码在哪里——为什么会变成这样呢……第一次有了想写分析的源码,又有了写博客的时间。两件快乐事情重合在一起。而这两份快乐,又给我带来更多的快乐。得到的,本该是像梦境一般幸福的时间……但是,为什么,会变成这样呢……还好顺路看到MS开源的一个基于内存池的MemoryStream替代实现,看起来用这个水一篇文章妥妥的。

ps: 虽然在标题上扯了.net,不过说实话除了代码是用c#外,我自己也想不出来其中的技术概念与.net有几毛钱关系。如果愿意的话,

绝大部分语言都可以实现来着。

var ms = new MemoryStream()

在讨论对象池之前,咱们先来看一段简单的代码。

var ms = new MemoryStream();
DataContractSerializer serializer = new DataContractSerializer(typeof(Model));
serializer.WriteObject(ms, model);

这段代码采用DataContractSerializer序列化一个类型为Model的对象,没有什么特殊的技巧可言。大部分情况下也没必要去折腾这块代码。

不过,在实际系统中,此处还是存在着一个不大不小的问题——每次运行这个代码段都会新建一个MemoryStream,然后往这个MemoryStream中写入字节流。

看上去MemoryStream非常神奇,它是一个基于内存,可以不断写入(只要还有内存)的流。不过如果查看源码,就会发现它实际上还是基于byte[]实现的,每次扩展容量时,都新建一个足够大的byte[]

if (_expandable && value != _capacity) {
if (value > 0) {
byte[] newBuffer = new byte[value];
if (_length > 0) Buffer.InternalBlockCopy(_buffer, 0, newBuffer, 0, _length);
_buffer = newBuffer;
}
else {
_buffer = null;
}
_capacity = value;
}

别的不说,默默新建的那一堆byte[]对GC产生的压力,甚至有可能触发过多的gen2 gc,对提高系统的吞吐量都是非常不利的。最好能重复利用已经分配出来的内存空间,让这些byte[]活的越久越好。于是我们就得把用完的MemoryStream回收回来,洗洗更健康再用。

对象池

其实对象池的基本思路非常简单,无非分为以下几步:

  1. 创建池
  2. 使用者向池申请资源
  3. 池分配可用的对象(如果没有现成的,则创建个新对象)
  4. 使用者用完后将对象还回池中

无论是ADO.NET中的连接池,还是ThreadPool线程池,抑或接下来要说到的RecyclableMemoryStream内存池,都离不开

这个思路。

下载源码

由于原版的RecyclableMemoryStream中的功能完整,不太适合讲解对象池的原理之用,故特地裁剪了个

版本出来——虽然也能用,不过在实际项目中还是推荐用nuget安装完整版的。

简化版请点此处

static RecyclableMemoryStreamManager recyclableMemoryManager = new Microsoft.IO.RecyclableMemoryStreamManager()

对照对象池的思路,码农说:“要有池。”就有了池。

码农看池是全局的,就把池赋给了静态字段。

码农称池为recyclableMemoryManager,这是头一步。

唯一重要的就是下面这行代码

this.smallPool = new ConcurrentStack<byte[]>();

………………只是简单的建立了个栈来管理内存块………………

对了,前面忘记补充说明下,为了充分管理内存的使用,RecyclableMemoryStream中并不是直接将MemoryStream

管理起来,而是另外实现了个MemoryStream,并改为由池来管理固定大小的byte[]内存块。

此处的smallPool就是核心的内存块池。

var ms = reusableMemoryStreamManager.GetStream()

接下来就是使用者向池申请资源了——才怪!

由于RecyclableMemoryStream的实现是基于内存块管理的思路,故而创建一个新的RecyclableMemoryStream

实例不需要特别做什么。

ReusableMemoryStream.EnsureCapacity()

接下来就是使用者向池申请资源了。无论是向流中写入数据,还是通过CapacitySetLength方法

显示设置流的长度,最终都需要确保流中有足够的内存空间。废话不说看源码:

private void EnsureCapacity(int newCapacity)
{
while (this.Capacity < newCapacity)
{
blocks.Add((this.memoryManager.GetBlock()));
}
}

这里的blocks是一个List<byte[]>,用来存储已分配到的内存块。memoryManagerRecyclableMemoryStreamManager类型的对象池——

也就是最开始我们创建的recyclableMemoryManager啦。

RecyclableMemoryStreamManager.GetBlock()

前面EnsureCapacity方法的关键就是调用GetBlock方法获取内存块——也就是对象池分配可用的对象。继续废话不说上代码

internal byte[] GetBlock()
{
byte[] block;
if (!this.smallPool.TryPop(out block))
{
// We'll add this back to the pool when the stream is disposed
// (unless our free pool is too large)
block = new byte[this.BlockSize];
}
return block;
}

别说我偷懒,如此简单的代码还需要解释么?

ReusableMemoryStream.Dispose()

上面偷懒我认了,接下来我坚决不偷懒——我要贴两块!

使用者用完后将对象还回池中,通常需要用户释放资源——.Net下通常意味着实现IDisposable接口。

ReusableMemoryStream的Dispose方法写的比较完整,如果对Dispose方法的正确姿势没有研究的话推荐看看。

但是话说回来,咱们现在讨论的是对象池,所以关键的代码只有一行:

this.memoryManager.ReturnBlocks(this.blocks);

而RecyclableMemoryStreamManager的ReturnBlocks方法关键代码如下:


本来想写篇关于System.Collections.Immutable中提供的ImmutableList里一些实现细节来着,
结果一时想不起来源码在哪里——为什么会变成这样呢……第一次有了想写分析的源码,又有了写博客的时间。
两件快乐事情重合在一起。而这两份快乐,又给我带来更多的快乐。得到的,本该是像梦境一般幸福的时间……
但是,为什么,会变成这样呢……还好顺路看到MS开源的一个基于内存池的MemoryStream替代实现,看起来用这个水一篇文章妥妥的。 ps: 虽然在标题上扯了.net,不过说实话除了代码是用c#外,我自己也想不出来其中的技术概念与.net有几毛钱关系。如果愿意的话,
绝大部分语言都可以实现来着。 ## var ms = new MemoryStream() 在讨论对象池之前,咱们先来看一段简单的代码。 ```c#
var ms = new MemoryStream();
DataContractSerializer serializer = new DataContractSerializer(typeof(Model));
serializer.WriteObject(ms, model);

这段代码采用DataContractSerializer序列化一个类型为Model的对象,没有什么特殊的技巧可言。大部分情况下也没必要去折腾这块代码。

不过,在实际系统中,此处还是存在着一个不大不小的问题——每次运行这个代码段都会新建一个MemoryStream,

然后往这个MemoryStream中写入字节流。

看上去MemoryStream非常神奇,它是一个基于内存,可以不断写入(只要还有内存)的流。不过如果查看

源码,就会发现它实际上还是基于byte[]实现的,每次扩展容量时,都新建一个足够大byte[]

if (_expandable && value != _capacity) {
if (value > 0) {
byte[] newBuffer = new byte[value];
if (_length > 0) Buffer.InternalBlockCopy(_buffer, 0, newBuffer, 0, _length);
_buffer = newBuffer;
}
else {
_buffer = null;
}
_capacity = value;
}

别的不说,默默新建的那一堆byte[]对GC产生的压力,甚至有可能触发过多的gen2 gc

对提高系统的吞吐量都是非常不利的。最好能重复利用已经分配出来的内存空间,让这些byte[]活的越久越好。于是我们就得把用完的MemoryStream回收回来,

洗洗更健康再用。

对象池

其实对象池的基本思路非常简单,无非分为以下几步:

  1. 创建池
  2. 使用者向池申请资源
  3. 池分配可用的对象(如果没有现成的,则创建个新对象)
  4. 使用者用完后将对象还回池中

无论是ADO.NET中的连接池,还是ThreadPool线程池,抑或接下来要说到的RecyclableMemoryStream内存池,都离不开这个思路。

下载源码

由于原版的RecyclableMemoryStream中的功能完整,不太适合讲解对象池的原理之用,故特地裁剪了个版本出来——虽然也能用,不过在实际项目中还是推荐用nuget安装完整版的。

简化版请点此处

static RecyclableMemoryStreamManager recyclableMemoryManager = new Microsoft.IO.RecyclableMemoryStreamManager()

对照对象池的思路,码农说:“要有池。”就有了池。

码农看池是全局的,就把池赋给了静态字段。

码农称池为recyclableMemoryManager,这是头一步。

唯一重要的就是下面这行代码

this.smallPool = new ConcurrentStack<byte[]>();

………………只是简单的建立了个栈来管理内存块………………

对了,前面忘记补充说明下,为了充分管理内存的使用,RecyclableMemoryStream中并不是直接将MemoryStream管理起来,而是另外实现了个MemoryStream,并改为由池来管理固定大小的byte[]内存块。此处的smallPool就是核心的内存块池。

var ms = reusableMemoryStreamManager.GetStream()

接下来就是使用者向池申请资源了——才怪!

由于RecyclableMemoryStream的实现是基于内存块管理的思路,故而创建一个新的RecyclableMemoryStream实例时不需要特别做什么。

ReusableMemoryStream.EnsureCapacity()

接下来就是使用者向池申请资源了。无论是向流中写入数据,还是通过CapacitySetLength方法显式设置流的长度,最终都需要确保流中有足够的内存空间。废话不说看源码:

private void EnsureCapacity(int newCapacity)
{
while (this.Capacity < newCapacity)
{
blocks.Add((this.memoryManager.GetBlock()));
}
}

这里的blocks是一个List<byte[]>,用来存储已分配到的内存块。memoryManagerRecyclableMemoryStreamManager类型的对象池——

也就是最开始我们创建的recyclableMemoryManager啦。

RecyclableMemoryStreamManager.GetBlock()

前面EnsureCapacity方法的关键就是调用GetBlock方法获取内存块——也就是对象池分配可用的对象。继续废话不说上代码

internal byte[] GetBlock()
{
byte[] block;
if (!this.smallPool.TryPop(out block))
{
// We'll add this back to the pool when the stream is disposed
// (unless our free pool is too large)
block = new byte[this.BlockSize];
}
return block;
}

别说我偷懒,如此简单的代码还需要解释么?

ReusableMemoryStream.Dispose()

上面偷懒我认了,接下来我坚决不偷懒——我要贴两块!

使用者用完后将对象还回池中,通常需要用户释放资源——.Net下通常意味着实现IDisposable接口。

ReusableMemoryStream的Dispose方法写的比较完整,如果对Dispose方法的正确姿势没有研究的话推荐看看。但是话说回来,咱们现在讨论的是对象池,所以关键的代码只有一行:

this.memoryManager.ReturnBlocks(this.blocks);

而RecyclableMemoryStreamManager的ReturnBlocks方法关键代码如下:

foreach (var block in blocks)
{
this.smallPool.Push(block);
}

到这里,内存块又回归内存池之海了(又是池又是海的……你要理解!理解!)。

结束语

只要理解了对象池技术的原理,就会发现这个技术一点都不复杂——虽然实际工程中可能还需要编写大量的代码。其实很多听上去玄乎的技术,解释开了也不过如此。

Adiós

对象池与.net—从一个内存池实现说起的更多相关文章

  1. nginx源码分析—内存池结构ngx_pool_t及内存管理

    Content 0. 序 1. 内存池结构 1.1 ngx_pool_t结构 1.2 其他相关结构 1.3 ngx_pool_t的逻辑结构 2. 内存池操作 2.1 创建内存池 2.2 销毁内存池 2 ...

  2. 内存池技术(UVa 122 Tree on the level)

    内存池技术就是创建一个内存池,内存池中保存着可以使用的内存,可以使用数组的形式实现,然后创建一个空闲列表,开始时将内存池中所有内存放入空闲列表中,表示空闲列表中所有内存都可以使用,当不需要某一内存时, ...

  3. nginx源代码分析之内存池实现原理

    建议看本文档时结合nginx源代码. 1.1   什么是内存池?为什么要引入内存池? 内存池实质上是接替OS进行内存管理.应用程序申请内存时不再与OS打交道.而是从内存池中申请内存或者释放内存到内存池 ...

  4. 基于C/S架构的3D对战网络游戏C++框架 _05搭建系统开发环境与Boost智能指针、内存池初步了解

    本系列博客主要是以对战游戏为背景介绍3D对战网络游戏常用的开发技术以及C++高级编程技巧,有了这些知识,就可以开发出中小型游戏项目或3D工业仿真项目. 笔者将分为以下三个部分向大家介绍(每日更新): ...

  5. 【uTenux实验】内存池管理(固定内存池和可变内存池)

    1.固定内存池管理实验 内存管理是操作系统的一个基础功能.uTenux的内存池管理函数提供了基于软件的内存池管理和内存块分配管理.uTenux的内存池有固定大小的内存池和大小可变的内存池之分,它们被看 ...

  6. nginx——内存池篇

    nginx--内存池篇 一.内存池概述 内存池是在真正使用内存之前,预先申请分配一定数量的.大小相等(一般情况下)的内存块留作备用.当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续 ...

  7. C++实现简单的内存池

    多进程编程多用在并发服务器的编写上,当收到一个请求时,服务器新建一个进程处理请求,同时继续监听.为了提高响应速度,服务器采用进程池的方法,在初始化阶段创建一个进程池,池中有许多预创建的进程,当请求到达 ...

  8. 定长内存池之BOOST::pool

    内存池可有效降低动态申请内存的次数,减少与内核态的交互,提升系统性能,减少内存碎片,增加内存空间使用率,避免内存泄漏的可能性,这么多的优点,没有理由不在系统中使用该技术. 内存池分类: 1.      ...

  9. 不定长内存池之apr_pool

    内存池可有效降低动态申请内存的次数,减少与内核态的交互,提升系统性能,减少内存碎片,增加内存空间使用率,避免内存泄漏的可能性,这么多的优点,没有理由不在系统中使用该技术. 内存池分类: 1.      ...

随机推荐

  1. Spark Idea Maven 开发环境搭建

    一.安装jdk jdk版本最好是1.7以上,设置好环境变量,安装过程,略. 二.安装Maven 我选择的Maven版本是3.3.3,安装过程,略. 编辑Maven安装目录conf/settings.x ...

  2. Hadoop HDFS编程 API入门系列之HDFS_HA(五)

    不多说,直接上代码. 代码 package zhouls.bigdata.myWholeHadoop.HDFS.hdfs3; import java.io.FileInputStream;import ...

  3. C# 分部类与分部方法

    一.定义 分部方法是指能够使编码人员跨多个代码文件实现类型的语法.简而言之.它可以让我们在一个文件中构建方法原型,而在另一个文件中实现 使用分部方法和分部类需要使用关键词partial,且紧靠在cla ...

  4. PHP 用html方式输出Excel文件时的数据格式设置

    1) 文本:vnd.ms-excel.numberformat:@ 2) 日期:vnd.ms-excel.numberformat:yyyy/mm/dd 3) 数字:vnd.ms-excel.numb ...

  5. ios delegate, block, NSNotification用法

    ios中实现callback可以通过两种方法,委托和NSNotification 委托的话是一对一的关系,例如一个UIViewController里有一个tableView, 将该viewContro ...

  6. VC++ operate excel

    利用VC操作Excel的方法至少有两种 1 .利用ODBC把Excel文件当成数据库文件,来进行读.写.修改等操作,网上有人编写了CSpreadSheet类,提供支持. 2. 利用Automation ...

  7. 从零开始搭建Docker Swarm集群

    从零开始搭建Docker Swarm集群 检查节点Docker配置 1. 打开Docker配置文件(示例是centos 7)vim /etc/sysconfig/docker2. 添加-H tcp:/ ...

  8. Python-内置类属性

    Python内置类属性 __dict__ : 类的属性(包含一个字典,由类的数据属性组成) __doc__ :类的文档字符串 __name__: 类名 __module__: 类定义所在的模块(类的全 ...

  9. 树莓派(raspberry pi)学习4: 更改键盘布局(转)

    树莓派(raspberry pi)用了几次后,发现键盘老是按错,一些字符打不出来或打错 这个问题,折腾我半天.还是把心得分享一下吧 上网查,发现是键盘布局不对,树莓派(raspberry pi)是英国 ...

  10. C盘实际占用容量比显示的要少

    1.问题 服务器是Window Server 2008 R2,就几天时间,60G的C盘容量一下子满了,选中所有的文件,占用才20多G. 2.原因 1).有的文件没有系统管理员权限,大小不会显示出来. ...