首页 > 其他分享 >.NET性能优化-使用RecyclableMemoryStream替代MemoryStream

.NET性能优化-使用RecyclableMemoryStream替代MemoryStream

时间:2023-01-03 10:44:08浏览次数:44  
标签:调用 MemoryStream 内存 使用 缓冲区 NET RecyclableMemoryStream

 

提到 MemoryStream 大家可能都不陌生,在编写代码中或多或少有使用过;比如Json序列化反序列化、导出PDF/Excel/Word、进行图片或者文字处理等场景。但是如果使用它高频、大数据量处理这些数据,就存在一些性能陷阱。

今天给大家带来的这个优化技巧其实就是池化 MemoryStream 的版本 RecyclableMemoryStream ,它通过池化 MemoryStream 底层buffer来 降低内存占用率、GC暂停时间和GC次数达到提升性能目的。

它的开源库地址如下链接:

https://github.com/microsoft/Microsoft.IO.RecyclableMemoryStream

使用它也非常简单,直接安装对应的Nuget包即可,目前最新版本是 2.2.1 版本。

// 命令行安装 dotnet addpackage Microsoft.IO.RecyclableMemoryStream --version2.2.1 // csproj 安装 <PackageReference Include="Microsoft.IO.RecyclableMemoryStream "Version="2.2.1 "/>

然后创建一个 RecyclableMemoryStreamManager 对象,即可使用它的 GetStream 方法来获取一个池化的流,当然使用完这个流以后需要调用 Dispose 方法将其归还到池中,也可以使用 using 模式来释放。

classProgram{privatestaticreadonlyRecyclableMemoryStreamManagermanager =newRecyclableMemoryStreamManager();staticvoidMain(string[]args ){varsourceBuffer =newbyte[]{0,1,2,3,4,5,6,7};using(varstream =manager .GetStream()){stream .Write(sourceBuffer ,0,sourceBuffer .Length );}}}

在创建 RecyclableMemoryStreamManager 和 GetStream 时有很多选项,可以设置底层buffer的大小、为流进行命名隔离等精细化的选项,这些大家可以看官方文档了解,本文不再赘述。

性能比较

为了直观的比较性能,我构建了一个Benchmark,这个基准测试分别使用 MemoryStream 和 RecyclableMemoryStream 实现数据缓冲的功能,下面是测试代码:

publicclassBenchmarkRecyclableMemoryStream{// 生成随机数privatestaticreadonlyRandomRandom =new(1024);// 填充的数据privatestaticreadonlybyte[]Data =Enumerable .Range(0,81920).Select(d =>(byte)d ).ToArray();// 每次随机填充privatestaticreadonlyint[]DataLength =Enumerable .Range(0,1000).Select(d =>Random .Next(10240,81920)).ToArray();// RecyclableManagerprivatestaticreadonlyRecyclableMemoryStreamManagerManager =new();[Benchmark(Baseline =true)]publiclongUseMemoryStream(){varsum =0L;for(inti =0;i <DataLength .Length ;i ++){usingvarstream =newMemoryStream();stream .Write(Data ,0,DataLength [i ]);sum +=stream .Length ;}returnsum ;}[Benchmark]publiclongUseRecyclableMemoryStream(){varsum =0L;for(inti =0;i <DataLength .Length ;i ++){usingvarstream =Manager .GetStream();stream .Write(Data ,0,DataLength [i ]);sum +=stream .Length ;}returnsum ;}}

下方是测试的结果,可以看到使用 RecyclableMemoryStream 比直接使用 MemoryStream 在内存和速度上有很大的优势。

  • 执行效率快 51%
  • 内存分配要低 99.4%
工作原理

RecyclableMemoryStream 提升GC性能的方式是通过将缓冲区分配和保持在第二代堆,这能减少FullGC的频率,另外如果您设置的缓冲区大小超过85,000字节,那么缓冲区将分配在LOH上,GC不会经常扫描这些对象堆。

RecyclableMemoryStreamManager 类维护了两个独立的对象池:

  • 小型池 :保存小型缓冲区(可配置大小),默认情况下用于所有正常的读、写操作,多个小的缓冲区能链接在一起,形成单独的 Stream 。
  • 大型池 :保存大型缓冲区,只有在必须需要单个且连续缓冲区才使用,比如调用 GetBuffer 方法,它可以创建比单个缓冲区大的多的 Stream ,最大不超过.NET对数组类型的限制。

RecyclableMemoryStream 首先会使用一个小的缓冲区,随着写入数据的增多,会将其它缓冲区链接起来组合使用。如果您调用了 GetBuffer 方法,并且已有的数据大于单个小缓冲区的容量,那么就会被转换为大缓冲区。

另外您还可以为 Stream 设置初始容量,如果容量大于单个缓冲区大小,会在一开始就链接好多个块,当然也可以直接分配大型缓冲区,只需将 asContiguousBuffer 设置为true。

大型池有两个版本:

  • 线性 (默认):指定一个倍数和最大的大小,然后创建一个缓冲区数组,从(1x倍数)、(2x倍数)一直到最大值。
  • 指数 :缓冲区不是线性增长而是指数增长,每个槽大小将增加一倍。
如下图所示:

那么您应该用哪一个?这取决于您的业务场景。如果您的缓冲区大小不可预测,那么线性缓冲区可能更合适。如果您知道不可能分配较长的流长度,但是可能有很多较小尺寸的流,那么选择指数版本可能会导致较少的总体内存使用。

缓冲区是在第一次被请求时按需创建的。使用完 Stream 后,这些缓冲区将通过 RecyclableMemoryStream 的 Dispose 方法返回到池中。当这种返回发生时, RecyclableMemoryStreamManager 将使用属性 MaximumFreeSmallPoolBytes 和 MaximumFreeLargePoolBytes 来决定是否将这些缓冲区放回池中,或者让它们离开(从而被垃圾收集)。正是通过这些属性,你决定了你的池子可以增长到多大。如果你把这些属性设置为0,你就会有无限制的池增长,这与内存泄漏基本上没有区别。对于每一个应用程序,你必须通过分析和实验来确定内存池大小和垃圾收集之间的适当平衡。

如果忘记调用流的 Dispose 方法,可能会导致内存泄漏。为了帮助您避免这种情况,每个流都有一个终结器,一旦没有更多对流的引用,CLR 将调用该终结器。此终结器将引发有关泄漏流的事件或记录有关泄漏流的消息。

请注意,由于性能原因,缓冲区从来没有预先初始化或归零。您有责任确保它们的内容是有效和安全的,可以使用缓冲区回收。

使用指南

虽然这个库力求非常通用化,并且不会对如何使用它施加太多限制,但是它的目的是减少由于频繁的大量分配而产生的垃圾收集的成本。因此,以下是一些对你有用的通用使用指南:

  1. 将 blockSize 、 largeBufferMultiple 、 maxBufferSize 、 MaximumFreeLargePoolBytes 和 MaximumFreeSmallPoolBytes 属性设置为符合你的应用和资源要求的合理值。如果你不设置 MaximumFreeLargePoolBytes 和 MaximumFreeSmallPoolBytes ,就有可能出现无限制的内存增长!
  2. 每个流总是被精确地 Dispose 一次。
  3. 大多数应用程序不应该调用 ToArray ,如果可能,应该避免调用 GetBuffer 。相反,使用 GetReadOnlySequence 来读取,使用 IBufferWriter 方法 GetSpan 、 GetMemory 和 Advance 来写入。还有一些杂七杂八的 CopyTo 和 WriteTo 方法,可能很方便。重点是要尽可能避免产生不必要的GC压力。
  4. 通过实验找到适合你情况的设置。

在你尝试用这个库来优化你的方案之前,对垃圾收集器有一定的了解是一个非常好的主意。像垃圾收集这样的文章,或者像《编写高性能的.NET代码》这样的书,将帮助你理解这个库的设计原则。

在配置选项时,要考虑这样的问题。

  • 我期望的流的长度分布是怎样的?
  • 有多少个流会在同一时间被使用?
  • GetBuffer 是否经常被调用?我需要多大程度的使用大型池缓冲区?
  • 我需要对活动高峰有多大的弹性?即我应该保留多少空闲字节以备不时之需?
  • 我在要使用的机器上有哪些物理内存限制?
总结

本文中介绍了一个通用的 MemoryStream 池化库,使用它能显著的提升你系统的性能,你几乎可以在任何场景使用 RecyclableMemoryStream 替代 MemoryStream 。要知道在我们性能评测中, RecyclableMemoryStream 比 MemoryStream 快51%,而且它能节省99.4%的内存分配。

标签:调用,MemoryStream,内存,使用,缓冲区,NET,RecyclableMemoryStream
From: https://www.cnblogs.com/robertyao/p/17021386.html

相关文章

  • Kubernetes监控手册09-监控ETCD
    写在前面ETCD是Kubernetes控制面的重要组件和依赖,Kubernetes的各类信息都存储在ETCD中,所以监控ETCD就显得尤为重要。ETCD在Kubernetes中的架构角色如下(只与APIS......
  • Kubernetes监控手册05-监控Kubelet
    上一篇我们介绍了如何监控Kube-Proxy,Kube-Proxy的/metrics接口没有认证,相对比较容易,这一篇我们介绍一下Kubelet,Kubelet的监控相比Kube-Proxy增加了认证机制,相对更复杂一些......
  • .NET性能优化-使用RecyclableMemoryStream替代MemoryStream
    提到MemoryStream大家可能都不陌生,在编写代码中或多或少有使用过;比如Json序列化反序列化、导出PDF/Excel/Word、进行图片或者文字处理等场景。但是如果使用它高频、大数据......
  • 【Packet Tracer】交换机Telnet远程登录配置
    1.什么是Telnet? 2.实验2.1拓扑图 (1)点击PC,分别配置PC0、PC1的IP地址/子网掩码   (2)点击交换机Switch0,选择CLI,进入命令行界面1>进入特权模式、全局配置模式......
  • virtualbox 上安装 esxi 6.7 不认识网卡( no network adapters)
    esxi6.7安装会有各种要求,CPU大于等于2颗,内存大于等于4G等等,还有就是网卡可能提示找不到,默认网卡类型不认识,选这个就行了: ......
  • MongoDB从入门到实战之.NET Core使用MongoDB开发ToDoList系统(1)-后端项目框架搭建
    前言:前面的四个章节我们主要讲解了MongoDB的相关基础知识,接下来我们就开始进入使用.NET7操作MongoDB开发一个ToDoList系统实战教程。MongoDB从入门到实战的相关教程......
  • kubenetes v1.25.0 升级 v 1.25.1
    升级K8s版本v1.25.0->v1.25.1:#升级之前v1.25.0:[root@k8s-master-1~]#kubectlgetnode-owideNAMESTATUSROLESAGEVERSIONI......
  • Kubernetes(k8s) kubectl annotate常用命令
    kubectl在$HOME/.kube目录中查找一个名为config的配置文件。可以通过设置KUBECONFIG环境变量或设置--kubeconfig参数来指定其它kubeconfig文件。本文主要介绍K......
  • Kubernetes 部署 ELK
    Kubernetes部署ELK项目背景k8s集群搭建后,平时运维过程中不仅要观察监控平台,查看集群运行情况,还要在集群出现问题时,对问题点进行及时定位,由于集群内pod过多后,日志定位比......
  • WPF+ASP.NET SignalR实现动态折线图
    在实际业务中,当后台数据发生变化,客户端能够实时的收到通知,而不是由用户主动的进行页面刷新才能查看,这将是一个非常人性化的设计。有没有那么一种场景,后台数据明明已经发生......