美文网首页
.NET高性能处理byte[]

.NET高性能处理byte[]

作者: 野生DBNull | 来源:发表于2021-09-02 16:31 被阅读0次

前言

最近遇到一个MQTT上传包体达到了5M,且上传速度较快,处理完毕后内存也没有下降的趋势,只有当服务器内存不够用的时候才会回收一小部分内存,导致内存飞涨,同一服务器上的其他服务根本无法提供稳定的服务。

原因分析

这种情况基本可以确定为byte[]太大,直接分配到堆上面了,而堆上的大对象要2代GC才能够回收,但是二代GC又懒又慢,啥时候回收这部分内存完全看心情。

解决思路

雪崩的时候没有一片雪花是无辜的

  1. 一旦出现这样的问题肯定存在多次流拷贝或者其他类似的问题,先要从源头查起,从缓冲区那边读取的时候就要控制拷贝次数,堆分配次数。
  2. 大对象肯定是要丢到堆中去的,所以要减少堆分配,固定一个长度的内存一直用是很好的解决方案
  3. 减少对数组的拷贝,直接使用指针去修改值是一个很好的手段

解决问题

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));

搞定 完事

相关文章

网友评论

      本文标题:.NET高性能处理byte[]

      本文链接:https://www.haomeiwen.com/subject/ezqcwltx.html