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

大佬您好,之前读了您的文章受益匪浅,我们有一个项目经常占用 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. 如何从Django项目中删除或隐藏应用

    1.项目的settings.py文件 INSTALLED_APPS中删除或者注释掉,这是针对数据库这一块儿的 2.项目的urls.py文件 删除或这注释掉应用的路径导入 urlpatterns中删除或 ...

  2. 【BotR】CLR堆栈遍历(Stackwalking in CLR)

    前言 在上一篇文章CLR类型系统概述里提到,当运行时挂起时, 垃圾回收会执行堆栈遍历器(stack walker)去拿到堆栈上值类型的大小和堆栈根.这里我们来翻译BotR里一篇专门介绍Stackwal ...

  3. value中放vue的参数

    <input type="text" v-model="userInfo.name"/>

  4. 洛谷P1719 最大加权矩形 (DP/二维前缀和)

    题目描述也没啥好说的,就是给你个你n*n的矩形(带权),求其中最大权值的子矩阵. 首先比较好想的就是二维前缀和,n<=120,所以可以用暴力. 1 #include<bits/stdc++ ...

  5. printf-库函数重定向、重载

    重定向:这是针对标准C语言语法来讲,指的是我们可以重新声明库函数并定义库函数的执行体 重载:这是CPP引入的新特性,即根据函数不同参数个数或参数类型来对同一函数名进行不同定义 C和CPP对重定向处理机 ...

  6. 函数柯里化实现sum函数

    需求 实现sum函数,使其可以传入不定长参数,以及不定次数调用 //示例 console.log(sum(1,2)(3)()) //6 console.log(sum(2,3,4,5)(1,2)(3) ...

  7. MySQL高可用集群MHA方案

    MySQL高可用集群MHA方案 爱奇艺在用的数据库高可用方案 MHA 是目前比较成熟及流行的 MySQL 高可用解决方案,很多互联网公司正是直接使用或者基于 MHA 的架构进行改造实现 MySQL 的 ...

  8. 微信小程序——悬浮按钮

    关键:    position: fixed; wxml: <navigator url="/pages/issue/index"><image class='i ...

  9. js函数组合

    纯函数和柯里化容易引起洋葱代码 函数组合可以让我们把细粒度的函数重新组合生成一个新的函数 函数组合并没有减少洋葱代码,只是封装了洋葱代码 函数组合执行顺序从右到左 满足结合律既可以把g和h组合 还可以 ...

  10. 线上kafka消息堆积,consumer掉线,怎么办?

    线上kafka消息堆积,所有consumer全部掉线,到底怎么回事? 最近处理了一次线上故障,具体故障表现就是kafka某个topic消息堆积,这个topic的相关consumer全部掉线. 整体排查 ...