.NET性能优化-推荐使用Collections.Pooled

article/2025/10/6 20:25:02

简介

性能优化就是如何在保证处理相同数量的请求情况下占用更少的资源,而这个资源一般就是CPU或者内存,当然还有操作系统IO句柄、网络流量、磁盘占用等等。但是绝大多数时候,我们就是在降低CPU和内存的占用率。

之前分享的内容都有一些局限性,很难直接改造,今天要和大家分享一个简单的方法,只需要替换几个集合类型,就可以达到提升性能和降低内存占用的效果。

要给大家分享一个类库,这个类库叫Collections.Pooled,从名字就可以看出来,它是通过池化内存来达到降低内存占用和GC的目的,后面我们会直接来看看它的性能到底怎么样,另外也会带大家看看源码,为什么它会带来这些性能提升。

Collections.Pooled

项目链接:https://github.com/jtmueller/Collections.Pooled

该库基于System.Collections.Generic中的类,这些类已经被修改,以利用新的System.Span<T>System.Buffers.ArrayPool<T>类库,达到减少内存分配,提高性能,并允许与现代API的更大的互操作性的目的。

Collections.Pooled支持.NETStandard2.0(.NET Framework 4.6.1+),以及针对.NET Core 2.1+的优化构建。

一套广泛的单元测试和基准已经从corefx移植过来。

测试总数:27501。通过:27501。失败:0。跳过:0。
测试运行成功。
测试执行时间:9.9019秒

如何使用

通过Nuget就可以很简单的安装这个类库,NuGet Version 。

Install-Package Collections.Pooled
dotnet add package Collections.Pooled
paket add Collections.Pooled

Collections.Pooled类库中,它针对我们常使用的集合类型都实现了池化的版本,和.NET原生类型的对比如下所示。

在使用时,我们只需要将对应的.NET原生版本换成Collections.Pooled版本就可以了,如下方的代码所示:

using Collections.Pooled;// 使用方式是一样的
var list = new List<int>();
var pooledList = new PooledList<int>();var dictionary = new Dictionary<int,int>();
var pooledDictionary = new PooledDictionary<int,int>();// 包括PooledSet、PooledQueue、PooledStack的使用方法都是一样的var pooledList1 = Enumerable.Range(0,100).ToPooledList();
var pooledDictionary1 = Enumerable.Range(0,100).ToPooledDictionary(i => i, i => i);

但是我们需要注意,Pooled类型实现了IDispose接口,它通过Dispose()方法将使用的内存归还到池中,所以我们需要在使用完Pooled集合对象以后调用它的Dispose()方法。或者可以直接使用using var关键字。

using Collections.Pooled;// 使用using var 会在pooled对象使用完毕后自动释放
using var pooledList = new PooledList<int>();
Console.WriteLine(pooledList.Count);// 使用using作用域 作用域结束以后就会释放
using (var pooledDictionary = new PooledDictionary<int, int>())
{Console.WriteLine(pooledDictionary.Count);
}// 手动调用Dispose方法
var pooledStack = new PooledStack<int>();
Console.WriteLine(pooledStack.Count);
pooledList.Dispose();

注意:使用Collections.Pooled内的集合对象最好需要释放掉它,不过不释放也没有关系,GC最终会回收它,只是它不能归还到池中,达不到节省内存的效果了。

由于它会复用内存空间,在将内存空间返回到池中的时候,需要对集合内的元素做处理,它提供了一个叫ClearMode的枚举供使用,定义如下:

namespace Collections.Pooled
{/// <summary>/// 这个枚举允许控制在内部数组返回到ArrayPool时如何处理数据。/// 数组返回到ArrayPool时如何处理数据。在使用默认选项之外的其他选项之前,请注意了解 /// 在使用默认值Auto之外的任何其他选项之前,请仔细了解每个选项的作用。/// </summary>public enum ClearMode{/// <summary>/// <para><code>Auto</code>根据目标框架有不同的行为</para>/// <para>.NET Core 2.1: 引用类型和包含引用类型的值类型在内部数组返回池时被清除。 不包含引用类型的值类型在返回池时不会被清除。</para>/// <para>.NET Standard 2.0: 在返回池之前清除所有用户类型,以防它们包含引用类型。 对于 .NET Standard,Auto 和 Always 具有相同的行为。</para>/// </summary>Auto = 0,/// <summary>/// The <para><code>Always</code> 设置的效果是在返回池之前总是清除用户类型。/// </summary>Always = 1,/// <summary>/// <para><code>Never</code> 将导致池化集合在将它们返回池之前永远不会清除用户类型。</para>/// </summary>Never = 2}
}

默认情况下,使用默认值Auto即可,如果有特殊的性能要求,知晓风险后可以使用Never。

对于引用类型和包含引用类型的值类型,我们必须在将内存空间归还到池的时候清空数组引用,如果不清除会导致GC无法释放这部分内存空间(因为元素的引用一直被池持有),如果是纯值类型,那么就可以不清空,在使用结构体替代类这篇文章中,我描述了引用类型和结构体(值类型)数组的存储区别,纯值类型没有对象头回收也无需GC介入。

性能对比

我没有单独做Benchmark,直接使用的开源项目的跑分结果,很多项目的内存占用都是0,那是因为使用的池化的内存,没有多余的分配

PooledList<T>

在Benchmark中循环向集合添加2048个元素,.NET原生的List<T>需要110us(根据实际跑分结果,图中的毫秒应该是笔误)和263KB内存,而PooledList<T>只需要36us0KB内存。

PooledDictionary<TKey, TValue>

在Benchmark中循环向字典添加10_0000个元素,.NET原生的Dictionary<TKey, TValue>需要11ms13MB内存,而PooledDictionary<TKey, TValue>只需要7ms0MB内存。

PooledSet<T>

在Benchmark中循环向哈希集合添加10_0000个元素,.NET原生的HashSet<T>需要5348ms2MB,而PooledSet<T>只需要4723ms0MB内存。

PooledStack<T>

在Benchmark中循环向栈添加10_0000个元素,.NET原生的PooledStack<T>需要1079ms2MB,而PooledStack<T>只需要633ms0MB内存。

PooledQueue<T>

在Benchmark中循环向队列添加10_0000个元素,.NET原生的PooledQueue<T>需要681ms1MB,而PooledQueue<T>只需要408ms0MB内存。

未手动释放场景

另外在上文中我们提到了Pooled的集合类型需要释放,但是不释放也没有太大的关系,因为GC会去回收。

private static readonly string[] List = Enumerable  .Range(0, 10000).Select(c => c.ToString()).ToArray();  
// 使用默认的集合类型  
[Benchmark(Baseline = true)]  
public int UseList()  
{  var list = new List<string>(1024);  for (var index = 0; index < List.Length; index++)  {  var item = List[index];  list.Add(item);  }  return list.Count;  
}  
// 使用PooledList 并且及时释放  
[Benchmark]  
public int UsePooled()  
{  using var list = new PooledList<string>(1024);  for (var index = 0; index < List.Length; index++)  {  var item = List[index];  list.Add(item);  }  return list.Count;  
}  
// 使用PooledList 不释放  
[Benchmark]  
public int UsePooledWithOutUsing()  
{  var list = new PooledList<string>(1024);  for (var index = 0; index < List.Length; index++)  {  var item = List[index];  list.Add(item);  }  return list.Count;  
}

Benchmark结果如下:

可以从上面的Benchmark结果可以得出结论。

  • 及时释放Pooled类型集合几乎不会触发GC和分配内存,从上图中它只分配了56Byte内存。

  • 就算不释放Pooled类型集合,因为它从池中分配内存,在进行ReSize扩容操作时还是会复用内存,另外跳过了GC分配内存初始化步骤,速度也比较快。

  • 最慢的就是使用普通集合类型,每次ReSize扩容操作都需要申请新的内存空间,GC也要回收之前的内存空间。

原理解析

如果大家看过我之前的博文你应该为集合类型设置初始大小和浅析C# Dictionary实现原理就可以知道,.NET BCL开发人员为了高性能的随机访问,这些基本集合类型的底层数据结构都是数组,我们以List<T>为例。

  • 创建新的数组来存储添加进来的元素。

  • 如果数组空间不够,那么就触发扩容操作,申请2倍的空间大小。构造函数代码如下,可以看到是直接创建的泛型数组:

public List(int capacity)
{if (capacity < 0)ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);if (capacity == 0)_items = s_emptyArray;else_items = new T[capacity];
}

那么如果想要池化内存,只需要把类库中使用new关键字申请的地方,改为使用池化的申请。这里和大家分享.NET BCL中的一个类型,叫ArrayPool,它提供了可重复使用的泛型实例的数组资源池,使用它可以降低对GC的压力,在频繁创建和销毁数组的情况下提升性能。

而我们Pooled类型的底层就是使用ArrayPool来共享资源池,从它的构造函数中,我们可以看到它默认使用的是ArrayPool<T>.Shared来分配数组对象,当然你也可以创建自己的ArrayPool来让它使用。

// 默认使用ArrayPool<T>.Shared池
public PooledList(int capacity, ClearMode clearMode, bool sizeToCapacity) : this(capacity, clearMode, ArrayPool<T>.Shared, sizeToCapacity) { }  // 分配数组使用 ArrayPool
public PooledList(int capacity, ClearMode clearMode, ArrayPool<T> customPool, bool sizeToCapacity)
{if (capacity < 0)ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);_pool = customPool ?? ArrayPool<T>.Shared;_clearOnFree = ShouldClear(clearMode);if (capacity == 0){_items = s_emptyArray;}else{_items = _pool.Rent(capacity);}if (sizeToCapacity){_size = capacity;if (clearMode != ClearMode.Never){Array.Clear(_items, 0, _size);}}}

另外在进行容量调整操作(扩容)时,会将旧的数组归还回线程池,新的数组也在池中获取。

 public int Capacity
{get => _items.Length;set{if (value < _size){ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);}if (value != _items.Length){if (value > 0){// 从池中分配数组var newItems = _pool.Rent(value);if (_size > 0){Array.Copy(_items, newItems, _size);}// 旧数组归还到池中ReturnArray();_items = newItems;}else{ReturnArray();_size = 0;}}}
}
private void ReturnArray()  
{  if (_items.Length == 0)  return;  try  {  // 归还到池中_pool.Return(_items, clearArray: _clearOnFree);  }  catch (ArgumentException)  {  // ArrayPool可能会抛出异常,我们直接吞掉 }  _items = s_emptyArray;  
}

另外作者使用了Span优化了Add、Insert等等API,让它们有更好的随机访问性能;另外还加入了TryXXX系列API,可以更方便的方式的使用它。比如List<T>类相比PooledList<T>就有多达170个修改。

总结

在我们线上实际的使用过程中,完全可以用Pooled提供的集合类型替代原生的集合类型,对降低内存占用率和P95延时有非常大的帮助。

另外就算忘记释放了,那性能也不会比使用原生的集合类型差多少。当然最好的习惯就是及时的释放它。


http://chatgpt.dhexx.cn/article/nk4Vuki9.shtml

相关文章

使用 TFDConnection 的 pooled 连接池

使用 TFDConnection 的 pooled 连接池 从开始看到这个属性&#xff0c;就一直认为他可以提供一个连接池管理功能&#xff0c; 苦于文档资料太少&#xff0c; 甚至在帮助中对该属性的使用都没有任何介绍&#xff0c;如果你搜索百度&#xff0c;也会发现基本没资料。 最后终于在…

Mybatis 连接池POOLED

1、连接池&#xff1a; 我们在实际开发中都会使用连接池。 因为它可以减少我们获取连接所消耗的时间。 2、mybatis中的连接池 在 Mybatis 中也有连接池技术&#xff0c;但是它采用的是自己的连接池技术。 在 Mybatis 的 SqlMapConfig.xml 配置文件中&#xff0c;通过来实 现 My…

Mybatis连接池介绍与分类 Mybatis使用POOLED UNPOOLED配置连接池的原理分析

一、连接池 1.概念&#xff1a;其实就是一个容器&#xff08;集合&#xff09;&#xff0c;存放数据库连接的容器。 当系统初始化好后&#xff0c;容器被创建&#xff0c;容器中会申请一些连接对象&#xff0c;当用户来访问数据库时&#xff0c;从容器中获取连接对象&#xf…

阶段3 1.Mybatis_07.Mybatis的连接池及事务_3 mybatis连接池的分类

2、mybatis中的连接池 mybatis连接池提供了3种方式的配置&#xff1a; 配置的位置&#xff1a; 主配置文件SqlMapConfig.xml中的dataSource标签&#xff0c;type属性就是表示采用何种连接池方式。 type属性的取值&#xff1a; POO…

Monkey Test简单介绍

什么是Monkey Test&#xff1f;顾名思义&#xff0c;就像一只猴子一样&#xff0c;它的下一步具有随机性。所以Monkey Test可以简单地理解为动作随机性测试&#xff01; Monkey是android模拟器或设备上运行的一个程序。它可以生成伪随机用户事件&#xff08;例如点击、触碰或滑…

monkey工具详解

第一部分&#xff1a;背景 1.为什么要开展压力测试&#xff1f; 提高产品的稳定性 提高产品的留存率 2.什么时候开始压力测试&#xff1f; 首轮功能测试通过后 下班后的夜间进行 第二部分&#xff1a;理论 1.什么是monkey&#xff1f; Monkey是发送伪随机用户事件的工…

monkey的基本使用

一、monkey介绍 1.功能&#xff1a;采用伪随机测试的方式&#xff0c;来完成app的稳定性测试 2.执行原理&#xff1a;通过monkey的shell脚本去执行安卓系统中自带的monkey.jar工具 3.通常用于盲测&#xff0c;压力测试和冒烟测试 1&#xff09;盲测&#xff1a;忽略功能和业务逻…

Monkey使用详解

App monkey 使用篇 安装包下载&#xff1a; 下载mumu模拟器地址&#xff1a;http://mumu.163.com/baidu/ 下载adb安装包 地 址&#xff1a;http://www.downza.cn/soft/219906.html Adb环境变量 配 置&#xff1a;在path里新建adb安装路径即可。 cmd 打开命令提示符窗口输入&a…

monkey的基本定义及基本使用(菜鸟学习中)

一.monkey的定义 1.(转自 https://blog.csdn.net/beyond_f/article/details/78543070 ) Monkey程序由Android系统自带&#xff0c;使用Java语言写成&#xff0c;在Android文件系统中的存放路径是&#xff1a;/system/framework/monkey.jar&#xff1b; Monkey.jar程序是由一…

Android测试--monkey详细到炸的总结

一、Monkey简介&#xff1a; Monkey是Android中的一个命令行工具&#xff0c;可以运行在模拟器里或者现实设备中&#xff0c;向系统发送伪随机的用户事件流&#xff08;点击、滑动、Application切换、横竖屏、应用关闭&#xff09;实现对正在开发的应用程序进行压力测试。monk…

APP稳定性测试利器 Monkey介绍、实战使用、日志分析

第一、Monkey简介 Monkey是什么&#xff1f; Monkey 是安卓官方提供的一个命令行工具&#xff0c;可以运行在Android模拟器和实体手机上。通过Monkey 来模拟用户的触摸、点击、滑动、系统按键的操作&#xff0c;来对APP进行压力测试、稳定性测试。换句话说&#xff0c;就是在乱…

Clumsy弱网、丢包测试工具

一、下载clumsy安装包&#xff0c;解压后打开clumsy.exe 二、ping www.baidu.com查看是否模拟成功 三、pc端模拟丢包和网络延迟 四、APP端模拟丢包和网络延迟 1.手机连接抓包工具charles 2.打开clumsy&#xff0c;在过滤器filtering中输入outbound and ip.DstAddr 192.168.…

【弱网】clumsy的filter语法设置

clumsy 官方说明 jagt WinDivert 的语法 https://github.com/basil00/Divert/wiki/WinDivert-Documentation#7-filter-languageDivert大神们的改版 clumsy-regoutbound 发送 inbound

弱网测试工具clumsy

clumsy 能在 Windows 平台下人工造成不稳定的网络状况&#xff0c;方便你调试应用程序在极端网络状况下的表现。 简介 利用封装 Winodws Filtering Platform 的WinDivert 库, clumsy 能实时的将系统接收和发出的网络数据包拦截下来&#xff0c;人工的造成延迟&#xff0c;掉包…

使用clumsy模拟网络延迟

为何模拟网络延迟 由于最近打算做及时对战类的游戏&#xff0c;对于及时对战类的游戏&#xff0c;首先要解决的问题就是网络延迟的问题。网络延迟对于游戏的体验至关重要。那么开发一款网络游戏要怎么解决这一问题呢。想要解决网络延迟&#xff0c;首先就要模拟网络延迟。那么…

[测试]Clumsy网络模拟工具

笔者最近因为需要在局域网内模拟一个实际情况&#xff08;即存在丢包、延迟等情况&#xff09;。网上搜了很多工具&#xff0c;大多基于linux。后来好不容易在网上搜索到一款2014年出现的网络模拟工具----Clumsy。 &#xff08;参考了http://jagt.github.io/clumsy/&#xff09…

curle(curley)

Queen Elizabeth is afraid of Mary. Bess Curle wrote the story. 这两句什么意思&#xff1f; 伊丽莎白女王害怕玛丽&#xff0c;Bess Curle写(说)出了这个道理。(这里的story成为道理&#xff0c;事实意义&#xff0c;不再只是故事的意思)&#xff0c;希望对你有帮助&#x…

网络抖动工具clumsy

网络抖动工具clumsy 下载与安装 下载地址&#xff1a;https://download.csdn.net/download/Asia1752/85192646 使用 说明&#xff1a; 1、Lag(延迟)&#xff0c;把数据包缓存一段时间后再发出&#xff0c;这样能够模拟网络延迟的状况。 2、Drop(掉包)&#xff0c;随机丢弃一…

网络丢包工具clumsy

以webrtc为例&#xff0c;未开始前 设置丢包率10%后&#xff0c;丢包会增加&#xff0c;如下图&#xff1a; Lag是延迟。设置后&#xff0c;增加网络延迟

Windows下网络环境模拟工具-Clumsy

公司设备需要模拟在弱网(如&#xff1a;延迟、丢包)环境下进行数据的传输&#xff0c;网上看资料找到一款工具Clumsy( 文章出处&#xff1a;Clumsy-Windows下网络环境模拟工具_clumsy工具_Hello&#xff0c;C&#xff01;的博客-CSDN博客),安装进行模拟&#xff0c;基本可以满足…