前两天在微信后台收到了读者的私信,问了一个这样的问题,由于私信回复有字数和篇幅限制,我在这里统一回复一下。读者的问题是这样的:

大佬您好,之前读了您的文章受益匪浅,我们有一个项目经常占用 7-8GB 的内存,使用了您推荐的ArrayPool以后降低到 4GB 左右,我还想着能不能继续优化,于是 dump 看了一下,发现是ArrayPool对应的一个数组有几万个对象,这个类有 100 多个属性。我想问有没有方法能复用这些对象?感谢!

根据读者的问题,我们摘抄出重点,现在他的数组已经得到池化,但是数组里面存的对象很大,从而导致内存很大

我觉得一个类有 100 多个属性应该是不太正常的,当然也可能是报表导出之类的需求,如果是普通类有 100 多个属性,那应该做一些抽象和拆分了。

如果是少部分的大对象需要重用,那其实可以使用ObjectPool,如果是数万个对象要重用,那么ObjectPool里面的 CAS 算法会成为瓶颈,那有没有更好的方式呢?其实解决方案就在ArrayPool类本身,可能大家平时没有注意过。

再聊 ArrayPool

我们再来回顾一下ArrayPool的用法,它的用法很简单,核心就是RentReturn两个方法,演示代码如下所示:

using System.Buffers;

namespace BenchmarkPooledList;

public class ArrayPoolDemo
{
public void Demo()
{
// get array from pool
var pool = ArrayPool<byte>.Shared.Rent(10);
try
{
// do something
}
finally
{
// return
ArrayPool<byte>.Shared.Return(pool);
}
}
}

其实对于上面的这个问题,ArrayPool已经有了解决方案,不知道大家有没有注意Return方法有一个默认参数clearArray=false.

public abstract void Return (T[] array, bool clearArray = false);

其中clearArray的含义就是当数组被归还到池时,是不是清空数组,也就是会不会将数组的所有元素重置为null,看下面的例子就明白了。

可以发现只要在归还到数组时不清空,那么第二次拿到的数组还是会保留值,基于这样一个设计,我们就可以在复用数组的同时复用对应的元素对象

性能比较

那么这样是否能解决之前提到的问题呢?我们很简单就可以构建一个测试用例,一个在代码里面使用new每次创建对象,另外一个尽量复用对象,为null时才创建。

// 定义一个大对象,放了40个属性
public class BigObject
{
public string P1 { get; set; }
public string P2 { get; set; }
public string P3 { get; set; }
.....
}

然后创建一个数据集,生成1000条数据,使用默认的方式,每次都new对象。

private static readonly string[] Datas = Enumerable.Range(0, 1000).Select(c => c.ToString()).ToArray();

[Benchmark(Baseline = true)]
public long UseArrayPool()
{
var pool = ArrayPool<BigObject?>.Shared.Rent(Datas.Length);
try
{
for (int i = 0; i < Datas.Length; i++)
{
pool[i] = new BigObject
{
P1 = Datas[i],
P2 = Datas[i],
P3 = Datas[i]
// .... 省略赋值代码
};
} return pool.Length;
}
finally
{
ArrayPool<BigObject?>.Shared.Return(pool);
}
}

另外一种方式就是复用对象池的对象,只有为null时才创建:

[Benchmark]
public long UseArrayPoolNeverClear()
{
var pool = ArrayPool<BigObject?>.Shared.Rent(Datas.Length);
try
{
for (int i = 0; i < Datas.Length; i++)
{
// 复用obj 为null时才创建
var obj = pool[i] ?? (pool[i] = new BigObject());
obj.P1 = Datas[i];
obj.P2 = Datas[i];
obj.P3 = Datas[i];
// .... 省略赋值代码
} return pool.Length;
}
finally
{
ArrayPool<BigObject?>.Shared.Return(pool, false);
}
}

可以看一下 Benchmark 的结果:

复用大对象的场景下,在没有造成性能的下降的情况下,内存分配几乎为0

ArrayObjectPool

之前笔者实现了一个类,优化了一下上面代码的性能,但是之前换了电脑,没有备份一些杂乱数据,现在找不到了。

具体优化原理是每一次都要进行null比较还是比较麻烦,而且如果能确定其数组不变的话,这些 null 判断是可以移除的。

凭借记忆写了一个 Demo,主要是确立在池里的数组是私有的,初始化一次以后就不需要再初始化,所以只要检测第一个元素是否为null就行,实现如下所示:

// 应该要实现IList<T>接口 和 ICollection<T> 等等的接口
// 不过这只是简单的demo 各位可以自行实现
public class ArrayObjectPool<T> : IDisposable // , IList<T>
where T : new()
{
// 创建一个独享的池
private static ArrayPool<T> _pool = ArrayPool<T>.Create(); private readonly T[] _items;
public ArrayObjectPool(int size)
{
Length = size;
_items = _pool.Rent(size);
if (_items[0] is not null) return;
// 如果第一个元素为null 说明是没初始化的
// 那么需要初始化
for (int i = 0; i < _items.Length; i++)
{
_items[i] = new T();
}
} // 为了安全只实现get
public T this[int index]
{
get
{
if (index < 0 || index > Length)
throw new ArgumentOutOfRangeException(nameof(index));
return _items[index];
}
set => throw new NotSupportedException();
} public int Length { get; } // 释放时返回数据
public void Dispose()
{
_pool.Return(_items);
} /// <summary>
/// 当ArrayPool过大时 可以重新创建
/// 旧的池就会被GC 回收
/// </summary>
public static void Flush()
{
_pool = ArrayPool<T>.Create();
}
}

同样的,对比了一下性能,因为会创建一个对象,所以内存占用比直接使用ArrayPool要高几十个字节,但是由于不用比较null,是实现里面最快的(当然也快不了多少,就 2%):

总结

我相信这个应该已经能回答提出的问题,我们可以在复用数组的时候复用数组所对应的对象,当然你必须确保复用对象没有副作用,比如复用了旧的脏数据

如果不是经常写这样的代码,像笔者一样封装一个ArrayObjectPool也没有必要,笔者本人也就写过那么一次,如果经常有这样的场景,那可以封装一个安全的ArrayObjectPool,想必也不是什么困难的事情。

感谢阅读,如果您有什么关于性能优化的疑问,欢迎在公众号留言。

.NET 性能优化交流群

相信大家在开发中经常会遇到一些性能问题,苦于没有有效的工具去发现性能瓶颈,或者是发现瓶颈以后不知道该如何优化。之前一直有读者朋友询问有没有技术交流群,但是由于各种原因一直都没创建,现在很高兴的在这里宣布,我创建了一个专门交流.NET 性能优化经验的群组,主题包括但不限于:

  • 如何找到.NET 性能瓶颈,如使用 APM、dotnet tools 等工具
  • .NET 框架底层原理的实现,如垃圾回收器、JIT 等等
  • 如何编写高性能的.NET 代码,哪些地方存在性能陷阱

希望能有更多志同道合朋友加入,分享一些工作中遇到的.NET 性能问题和宝贵的性能分析优化经验。由于已经达到 200 人,可以加我微信,我拉你进群: ls1075

.NET性能优化-ArrayPool同时复用数组和对象的更多相关文章

  1. .NET性能优化-为结构体数组使用StructLinq

    前言 本系列的主要目的是告诉大家在遇到性能问题时,有哪些方案可以去优化:并不是要求大家一开始就使用这些方案来提升性能. 在之前几篇文章中,有很多网友就有一些非此即彼的观念,在实际中,处处都是开发效率和 ...

  2. javascript性能优化之使用对象、数组直接量代替典型的对象创建和赋值

    1.典型的对象创建和赋值操作代码示例 var myObject = new Object(); myObject.name = "Nicholas"; myObject.count ...

  3. QF——UITableViewCell性能优化(视图复用机制)

    这几篇博客总结的不错: 点击进入 点击进入 总结起来方案一般有以下几种: 1.不使用透明视图: 2.减少视图的个数: 3.cell复用机制:(重点) 4.图片缓存: 5.网络请求使用非主线程. 6.预 ...

  4. JAXB性能优化

    前言: 之前在查阅jaxb相关资料的同时, 也看到了一些关于性能优化的点. 主要集中于对象和xml互转的过程中, 确实有些实实在在需要注意的点. 这边浅谈jaxb性能优化的一个思路. 案列: 先来构造 ...

  5. php多层数组与对象的转换实例代码

    通过json_decode(json_encode($object)可以将对象一次性转换为数组,但是object中遇到非utf-8编码的非ascii字符则会出现问题,比如gbk的中文,何况json_e ...

  6. iOS性能优化-数组、字典便利时间复杂

    上图是几种时间复杂度的关系,性能优化一定程度上是为了降低程序执行效率减低时间复杂度. 如下是几种时间复杂度的实例: O(1) return array[index] == value; 复制代码 O( ...

  7. .NET性能优化-复用StringBuilder

    在之前的文章中,我们介绍了dotnet在字符串拼接时可以使用的一些性能优化技巧.比如: 为StringBuilder设置Buffer初始大小 使用ValueStringBuilder等等 不过这些都多 ...

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

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

  9. 【腾讯Bugly干货分享】跨平台 ListView 性能优化

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/FbiSLPxFdGqJ00WgpJ94yw 导语 精 ...

  10. Java 性能优化之 String 篇

    原文:http://www.ibm.com/developerworks/cn/java/j-lo-optmizestring/ Java 性能优化之 String 篇 String 方法用于文本分析 ...

随机推荐

  1. 15. 第十四篇 安装CoreDNS

    文章转载自:https://mp.weixin.qq.com/s?__biz=MzI1MDgwNzQ1MQ==&mid=2247483850&idx=1&sn=4bfdb26f ...

  2. Service概述

    为何需要 Service Kubernetes 中 Pod 是随时可以消亡的(节点故障.容器内应用程序错误等原因).如果使用 Deployment 运行您的应用程序,Deployment 将会在 Po ...

  3. .NET6 使用 AutoFac (解析)

    一.Net 6环境下的.net core项目里如何使用Autofac实现依赖注入. 通常的,我们把其他服务注入到Controller时,使用.net core自带的依赖注入即可,但是如果我们要实现自定 ...

  4. 手把手教你使用LabVIEW人工智能视觉工具包快速实现图像读取与采集(含源码)

    目录 前言 一.工具包位置 二.图像采集与色彩空间转换 1.文件读写 2.实现图片读取 3.使用算子cvtColor实现颜色空间转换 三.从摄像头采集图像 1.Camera类 2.属性节点 3.实现摄 ...

  5. PTA2021 跨年挑战赛部分题解

    7-1 压岁钱 不用说 #include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn ...

  6. AcWing 最短Hamilton距离 (状压DP)

    题目描述 给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径. Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰 ...

  7. VScode开发STM32/GD32单片机-MakeFile工程JlinkRTT配置

    本次使用开发板为STM32F401CCU6,使用CubeMX配置一个Makefile工程 配置时候为内部时钟 工程选择makefile工程类型 只生成需要的文件 用VSCode打开后显示很多波浪线 选 ...

  8. 达梦dba_segments指定表名查询到的大小都包含哪些数据

    一.结论 dba_segments指定表名查询到的段大小包含索引.约束.表字段数据(包含LOB字段)(1)表(不包含LOB字段)创建默认分配2个簇,1个簇用于存放表结构及字段数据,1个簇用于存放clu ...

  9. IDEAidea导入Scala包

    IDEAidea导入Scala包 一.配置windows的scala的环境变量 二.IDEA导入scala插件 1.如图步骤导入IDEA的scala插件 三.添加本地的scala目录 这时候我们应该在 ...

  10. Linux 下配置 hosts 并设置免密登录

    Linux 下配置 hosts 并设置免密登录 作者:Grey 原文地址: 博客园:Linux 下配置 hosts 并设置免密登录 CSDN:Linux 下配置 hosts 并设置免密登录 说明 实现 ...