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

简介

性能优化就是如何在保证处理相同数量的请求情况下占用更少的资源,而这个资源一般就是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备注
List<T>PooledList<T>泛型集合类
Dictionary<TKey, TValue>PooledDictionary<TKey, TValue>泛型字典类
HashSet<T>PooledSet<T>泛型哈希集合类
Stack<T>Stack<T>泛型栈
Queue<T>PooledQueue<T>泛型队列

在使用时,我们只需要将对应的.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内存。
6df5330bfb3a0477673afeeaeec7b115.png

PooledDictionary<TKey, TValue>

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

PooledSet<T>

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

PooledStack<T>

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

PooledQueue<T>

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

未手动释放场景

另外在上文中我们提到了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结果如下:
91d6aa2e35fcc37c3affcf8022a38499.png
可以从上面的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优化了AddInsert等等API,让它们有更好的随机访问性能;另外还加入了TryXXX系列API,可以更方便的方式的使用它。比如List<T>类相比PooledList<T>就有多达170个修改。
1a64abc7f8a0816319e4ea45c44f8ba4.png

总结

在我们线上实际的使用过程中,完全可以用Pooled提供的集合类型替代原生的集合类型,对降低内存占用率和P95延时有非常大的帮助。
另外就算忘记释放了,那性能也不会比使用原生的集合类型差多少。当然最好的习惯就是及时的释放它。

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

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

相关文章

避免活跃性危险(第十章)

2019独角兽企业重金招聘Python工程师标准>>> 避免活跃性危险 在安全性与活跃性之间通常存在着某种制衡&#xff0c;我们使用加锁机制来确保线程安全&#xff0c;但如果过度地使用加锁&#xff0c;则可能导致“锁顺序死锁”。同样&#xff0c;我们使用线程池和信号量…

(10)C#偷懒的开始永无止境的循环?

本系列文章将会以通俗易懂的对话方式进行教学&#xff0c;对话中将涵盖了新手在学习中的一般问题。此系列将会持续更新&#xff0c;包括别的语言以及实战都将使用对话的方式进行教学&#xff0c;基础编程语言教学适用于零基础小白&#xff0c;之后实战课程也将会逐步更新。 若…

活照片 android,活照片app安卓

活照片app是当前国内一款最新的图片处理应用软件&#xff0c;能帮助大家快速进行最新的手机拍照、处理功能&#xff0c;当前活照片app已经推出了安卓、苹果版本&#xff0c;可以帮助大家一键修图&#xff0c;将你的图片变得更加有趣。活照片app功能&#xff1a;它可以让你的照片…

Jwt隐藏大坑,通过源码揭秘

前言JWT是目前最为流行的接口认证方案之一&#xff0c;有关JWT协议的详细内容&#xff0c;请参考&#xff1a;https://jwt.io/introduction今天分享一下在使用JWT在项目中遇到的一个问题&#xff0c;主要是一个协议的细节&#xff0c;非常容易被忽略&#xff0c;如果不是自己遇…

文件传输基础——Java IO流

一、文件的编码 1 package com.study.io;2 3 4 /**5 * 测试文件编码6 */7 public class EncodeDemo {8 9 /** 10 * param args 11 * throws Exception 12 */ 13 public static void main(String[] args) throws Exception { 14 String s&quo…

keepalived实现nginx的高可用(双主模型)

实验环境&#xff1a;RS1&#xff1a;rip&#xff08;172.16.125.7&#xff09;&#xff0c;安装httpd软件包&#xff1b;RS2&#xff1a;rip&#xff08;172.16.125.8&#xff09;&#xff0c;安装httpd软件包&#xff1b;director1&#xff08;7-1.lcs.com&#xff09;&#…

【必懂C++】第一个程序当然是HelloWorld呀 01

作者简介 作者名&#xff1a;1_bit 简介&#xff1a;CSDN博客专家&#xff0c;2020年博客之星TOP5&#xff0c;蓝桥签约作者。15-16年曾在网上直播&#xff0c;带领一批程序小白走上程序员之路。欢迎各位小白加我咨询我相关信息&#xff0c;迷茫的你会找到答案。系列教程将会…

打造操作系统根社区 统信Deepin屹立于浪潮之颠

如果把芯片比作信息系统的大脑的话&#xff0c;那么操作系统毫无疑问就是信息系统的灵魂。在过去几十年里&#xff0c;我国信息产业饱受“缺芯少魂”的困扰&#xff0c;国内市场基本被微软、谷歌、苹果、IBM、红帽等外商垄断。诚然&#xff0c;一些国内厂商推出过基于Fedora、u…

Androd之在图片右上角显示红色圆圈里面数字提醒

1 需求 在图片右上角显示红色圆圈里面数字提醒 2 效果如图 3 关键代码 item_loca.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_wid…

Bean

Bean spring中把一切配置到IOC容器(其实就是那个xml文件)里面的对象都称之为bean。 转载于:https://www.cnblogs.com/Renyi-Fan/p/7780935.html

【必懂C++】C++可真是个“固执”的小可爱 02

作者简介 作者名&#xff1a;1_bit 简介&#xff1a;CSDN博客专家&#xff0c;2020年博客之星TOP5&#xff0c;蓝桥签约作者。15-16年曾在网上直播&#xff0c;带领一批程序小白走上程序员之路。欢迎各位小白加我咨询我相关信息&#xff0c;迷茫的你会找到答案。系列教程将会…

Flutter之window系统下配置开发环境以及在Android Studio里面运行hello word

1 、window系统配置Flutter开发环境 1&#xff09;下载Flutter的SDK 如果电脑安装了Git&#xff0c;直接到https://github.com/flutter/flutter/这里下载&#xff0c;但是需要翻墙 git clone https://github.com/flutter/flutter.git 或者到lutter官网下载 https://flutter.d…

WPF效果第一百八十五篇之又玩TreeView

最近又有新的开发任务了,然后我提前瞄了一眼需要实现的效果;发现其中一个和我去年玩耍的有点类似;正好好久也没玩了,那就趁着这个机会再次学习一下;闲话也不多扯了,上效果:2、来看看我的实现方式:3、①是一个分组的数据模板<HierarchicalDataTemplate x:Key"GroupDataT…

ArcGIS实验教程——实验二十三:专题地图制作完整实验步骤

ArcGIS实验视频教程合集:《ArcGIS实验教程从入门到精通》(附配套实验数据) 一、实验描述 专题地图是一个非常复杂的过程,地图数据的符号化与注记标注,都是地图编制准备基础的地理数据。然而,要将准备好的地图数据,通过一幅完整的地图表达出来,还有很多工作,包括布局…

IOS 封装轮播图

轮播图为一种常见的方式&#xff0c;常用于各种网站&#xff0c;或者App中&#xff0c;当然&#xff0c;作为APP的启动视图也是不错的选择。 闲时封装了一个&#xff0c;仅供新手参考。 1.新建工程&#xff0c;建立轮播图类 建立一个空的工程&#xff0c;新建一个类&#xff0c…

分布式事务TCC补偿机制

文章目录 概述工作流程优缺点优点&#xff1a;缺点&#xff1a; 总结Java 示例代码 概述 TCC&#xff08;Try-Confirm-Cancel&#xff09;补偿机制是一种事务处理模式&#xff0c;用于确保分布式系统中的操作成功完成或在失败时进行补偿。TCC将一个事务拆分为三个阶段&#xf…

Flutter之导url_launcher包提示 A dependency may only have one source.

1、问题 flutter项目在pubspec.yaml导入url_launcher包&#xff0c;然后点击Pub get错误提示如下 F:\flutter_sdk\flutter\bin\flutter.bat --no-color pub get Running "flutter pub get" in flutter_1... Error on line 25, column 5 of …

这是我第一次使用代码创建出一个窗口【python 游戏实战 01】

前言 本系列文章将会以通俗易懂的对话方式进行教学&#xff0c;对话中将涵盖了新手在学习中的一般问题。此系列将会持续更新&#xff0c;包括别的语言以及实战都将使用对话的方式进行教学&#xff0c;基础编程语言教学适用于零基础小白&#xff0c;之后实战课程也将会逐步更新…

如何html中添加动态图片,把动态图片添加到视频画面中 视频添加自定义动态图片 视频加动态logo...

我前面也编写过关于视频添加动态图片的教程。前面所说的给是视频添加的动态图片是软件中自带的素材&#xff0c;虽然软件中带的动态图片种类繁多&#xff0c;但是不外乎有些时候软件中并没有我们要用的动态图片&#xff0c;这个时候我们就需要重外部添加啦&#xff0c;好多的软…

ASP.NET Core 集成AAD认证在Docker中运行时要注意的一个问题

最近我在准备一个分享&#xff0c;就是基于.NET 6.0的云原生开发Microsoft 365应用&#xff0c;这个看起来很高大上的东东&#xff0c;其实我理解主要就是能把应用容器化&#xff0c;便于与环境无关地进行分发和部署。如果理解有误&#xff0c;请大家纠正我。下面是其中的一个例…