前言
最近遇到一个MQTT上传包体达到了5M,且上传速度较快,处理完毕后内存也没有下降的趋势,只有当服务器内存不够用的时候才会回收一小部分内存,导致内存飞涨,同一服务器上的其他服务根本无法提供稳定的服务。
原因分析
这种情况基本可以确定为byte[]太大,直接分配到堆上面了,而堆上的大对象要2代GC才能够回收,但是二代GC又懒又慢,啥时候回收这部分内存完全看心情。
解决思路
雪崩的时候没有一片雪花是无辜的
- 一旦出现这样的问题肯定存在多次流拷贝或者其他类似的问题,先要从源头查起,从缓冲区那边读取的时候就要控制拷贝次数,堆分配次数。
- 大对象肯定是要丢到堆中去的,所以要减少堆分配,固定一个长度的内存一直用是很好的解决方案
- 减少对数组的拷贝,直接使用指针去修改值是一个很好的手段
解决问题
1. 解决数组租用的问题
既然心里已经大概有了解决的方案那这事情就很明朗了
一个固定数组的租用使用ArrayPool<T>
这个对象去进行数组的租用,但是这个玩意对于使用者有点不太友好,申请的长度并不是实际需要的长度,这对于一些需要使用固定长度进行计算的地方就很蛋疼了。所以需要对这个进行一个封装,将实际需要的长度存储起来作为这个数组的长度,超过的全部作废掉。
那这个类的基本设计就应该是下面这样的,有一个Length
去决定从ArrayPool<T>
租出来的数组到底有多少是可用长度。
public class HighPerfArray<T> : IDisposable {
/// <summary>
/// 数组池
/// </summary>
protected ArrayPool<T> ArrayPool { get; private set; }
/// <summary>
/// 源数组
/// </summary>
protected T[] SourceArray { get; private set; }
/// <summary>
/// 真实长度
/// </summary>
public int Length { get; private set; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="len">数组长度</param>
public HighPerfArray(int len)
{
ArrayPool = ArrayPool<T>.Shared;
Init(len);
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="pool">数组池</param>
/// <param name="len">长度</param>
public HighPerfArray(ArrayPool<T> pool, int len)
{
ArrayPool = pool;
Init(len);
}
protected virtual void Init(int len)
{
SourceArray = ArrayPool.Rent(len);
Length = len;
}
/// <summary>
/// 是否已经销毁
/// </summary>
public bool IsDispose { get; private set; }
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
if (!IsDispose)
{
// 回收数组
ArrayPool.Return(SourceArray, false);
SourceArray = null;
// 内存置空
IsDispose = true;
}
}
}
2.要对数组进行指针操作.NET给我们提供了Span
的API,但是这个没办法在Class中使用,所以只能使用它的同胞兄弟Memory<T>
,这些玩意在网上到处都是介绍的,我在这里就不介绍了。
3.如果要减少流拷贝就需要将这个类加上一个Clone
的函数,直接让其他人调用Clone函数,就不会出现流拷贝太多导致多次分配堆的问题。
那么综上所需,出来的封装类应该就是这样的
public class HighPerfArray<T> : IDisposable
{
/// <summary>
/// 数组池
/// </summary>
protected ArrayPool<T> ArrayPool { get; private set; }
/// <summary>
/// 源数组
/// </summary>
protected T[] SourceArray { get; private set; }
/// <summary>
/// 可操作数组
/// </summary>
public Memory<T> Memory { get; protected set; }
/// <summary>
/// 真实长度
/// </summary>
public int Length { get; private set; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="len">数组长度</param>
public HighPerfArray(int len)
{
ArrayPool = ArrayPool<T>.Shared;
Init(len);
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="pool">数组池</param>
/// <param name="len">长度</param>
public HighPerfArray(ArrayPool<T> pool, int len)
{
ArrayPool = pool;
Init(len);
}
protected virtual void Init(int len)
{
SourceArray = ArrayPool.Rent(len);
Memory = SourceArray.AsMemory(0, len);
Memory.Span.Clear(); // 保证这是一个清空了的数组
InitLength();
}
/// <summary>
/// 设置值
/// </summary>
/// <param name="index"></param>
/// <param name="data"></param>
public void SetValue(int index, T data)
{
Memory.Span[index] = data;
}
/// <summary>
/// 切割
/// </summary>
/// <param name="start"></param>
/// <param name="len"></param>
public void Slice(int start, int len)
{
Memory = Memory.Slice(start, len);
InitLength();
}
/// <summary>
/// 克隆本数组
/// </summary>
/// <returns></returns>
public HighPerfArray<T> Clone()
{
var cloneData = new HighPerfArray<T>(ArrayPool, Length);
for (int i = 0; i < Length; i++)
{
cloneData.SetValue(i, Memory.Span[i]);
}
return cloneData;
}
/// <summary>
/// 是否已经销毁
/// </summary>
public bool IsDispose { get; private set; }
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
if (!IsDispose)
{
// 回收数组
ArrayPool.Return(SourceArray, false);
SourceArray = null;
// 内存置空
Memory = null;
IsDispose = true;
}
}
~HighPerfArray()
{
Dispose(); // GC回收的时候手动释放资源
}
private void InitLength()
{
Length = Memory.Length;
}
}
再针对这个搞点拓展函数
public static class HighPerfArrayExt
{
#region Sum
public static int Sum(this HighPerfArray<int> data)
{
int sum = 0;
for (int i = 0; i < data.Length; i++)
{
sum += data.Memory.Span[i];
}
return sum;
}
public static float Sum(this HighPerfArray<float> data)
{
float sum = 0;
for (int i = 0; i < data.Length; i++)
{
sum += data.Memory.Span[i];
}
return sum;
}
public static double Sum(this HighPerfArray<double> data)
{
double sum = 0;
for (int i = 0; i < data.Length; i++)
{
sum += data.Memory.Span[i];
}
return sum;
}
#endregion
#region Max
public static int Max(this HighPerfArray<int> data)
{
int max = data.Memory.Span[0];
for (int i = 0; i < data.Length; i++)
{
if (max < data.Memory.Span[i])
max = data.Memory.Span[i];
}
return max;
}
public static float Max(this HighPerfArray<float> data)
{
float max = data.Memory.Span[0];
for (int i = 0; i < data.Length; i++)
{
if (max < data.Memory.Span[i])
max = data.Memory.Span[i];
}
return max;
}
public static double Max(this HighPerfArray<double> data)
{
double max = data.Memory.Span[0];
for (int i = 0; i < data.Length; i++)
{
if (max < data.Memory.Span[i])
max = data.Memory.Span[i];
}
return max;
}
#endregion
#region Min
public static int Min(this HighPerfArray<int> data)
{
int min = data.Memory.Span[0];
for (int i = 0; i < data.Length; i++)
{
if (min > data.Memory.Span[i])
min = data.Memory.Span[i];
}
return min;
}
public static float Min(this HighPerfArray<float> data)
{
float min = data.Memory.Span[0];
for (int i = 0; i < data.Length; i++)
{
if (min > data.Memory.Span[i])
min = data.Memory.Span[i];
}
return min;
}
public static double Min(this HighPerfArray<double> data)
{
double min = data.Memory.Span[0];
for (int i = 0; i < data.Length; i++)
{
if (min > data.Memory.Span[i])
min = data.Memory.Span[i];
}
return min;
}
#endregion
#region Average
public static int Average(this HighPerfArray<int> data)
{
return data.Sum() / data.Length;
}
public static float Average(this HighPerfArray<float> data)
{
return data.Sum() / data.Length;
}
public static double Average(this HighPerfArray<double> data)
{
return data.Sum() / data.Length;
}
#endregion
}
用法
拓展这个基础数组
public class ByteWave : HighPerfArray<byte>
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="len"></param>
public ByteWave(int len) : base(len)
{
}
/// <summary>
/// 克隆本数组
/// </summary>
/// <returns></returns>
public new ByteWave Clone()
{
var cloneData = new ByteWave(Length);
for (int i = 0; i < Length; i++)
{
cloneData.SetValue(i, Memory.Span[i]);
}
return cloneData;
}
}
使用这个基础数组
ByteWave wave = new ByteWave(waveLen * 2);
var span = wave.Memory.Span;
var tmpSpan = span.Slice(i * 2, 2);
BitConverter.TryWriteBytes(tmpSpan, Convert.ToInt16(tmpData));
搞定 完事
网友评论