高性能-GC3

带着问题去思考!大家好

今天我们继续优化。

避免对象固定

  对象固定(Pinning)是为了能够安全地将托管内存的引用传递给本机代码,最常见的用处就是 传递数组和字符串。如果不与本机代码进行交互,就完全不应该有对象固定的需求。

对象固定会把内存的地址固定下来, 垃圾回收器就无法移动这些对象,会增加内存碎片的可能,垃圾回收器会记住那些被固定的对象,以便能利用固定对象之间的空闲内存。过多的情况,还会导致内存碎片的产生和内存堆的扩大

  对象固定既可能是显式的,也可能是隐式的,使用GCHandleType.Pinned类型的GCHandle或者fixed关键字,可以完成显式对象固定,代码块必须标记为unsafe。用fixed/using用起来更方便,(fixed和GCHandle之间的区别类似于using和显式调用Dispose的差别),异步环境下是无法使用的,因为异步状态不能传递handle,也不能在回调方法中销毁handle。

避免使用终结方法

  非必要情况下,永远不要实现终结方法(Finalizer).终结方法是一段由垃圾回收器引发调用的代码,用于清理非托管资源。终结方法由一个独立的线程调用,排成队列依次完成,而且只有依次垃圾回收之后,对象被垃圾回收器声明已销毁,才会进行调用。如果类实现了终结方法,对象就一定会滞留在内存中,即便在垃圾回收时应该被销毁的情况下。

  如果实现了终结方法,那就必须同时实现IDisposable接口以启用显示清理,还要在Dispise方法中调用GC.SuppressFinalize(this)来吧对象从移除终结队列中移除。只要能在下次垃圾回收之前调用Dispose,就可以适时把对象清理干净。

  

  

class Foo:IDisposeable
{
   Foo(){Dispose(false);}
   public void Dispose()
   {
      Dispose(true);
      GC.SuppressFinalize(this);
    }  
   protected virtual void Dispose(bool disposing)
   {
     if(disposing)
     {
       this.manageResoure.Dispose();
      }
     //清理非托管资源
      UnsafeClose(this.handle);
   }
}

 

避免分配大对象

大对象的界限被为85000字节,任何大于这个值的对象都被认为是大对象,并独立的内存堆中进行分配,尽量避免,不仅是因为LOH的垃圾回收开销大,更多原因是因为内存碎片会导致内存用量不断增长

避免缓冲区复制

任何时候都应该避免复制数据,比如你已经把文件数据读入了MemorySteam(如果需要较大的缓存区,最好是用池化的流),一旦内存分配完毕,就应把此MemoryStream视为只读流,所有需要访问的MemoryStream的组件都能从同一份数据备份中读取数据,

如果需要表示整个缓冲区的一段,请使用ArraySegment<T>类,可用来代表底层byte[]类型缓冲区的一部分区域。此ArraySegment可以传给API,而与原来的流无关,甚至可以被绑定到一个新的MemoryStream对象上,这些过程都不会发生数据复制。

var memoryStream=new MemoryStream();
var segment=new ArraySegment<byte>(memoryStream.GetBuffer(),100,1024);
....
var blockStream=new MemoryStream(segment.Array,segment.Offset,segment.Count);

内存复制造成的最大影响肯定不是CPU,而是垃圾回收。

 对长期存活对象和大型对象进行池化

对象要么转瞬即逝,要么一直存活;要么在第0代垃圾回收时消失,要么就在第2代内存堆中一直留下去。有些对象基本上是静态的,伴随程序自然诞生,并在程序生存期间保持存活,还有一些对象看不出有一直存活的必要。但它们在程序上下文中体现出来的生存期,决定了它们会历经第0代(或者1代)垃圾回收并仍然存活。这时候应该考虑对这类对象进行池化。还有LOH中分配的对象,典型例子就是集合类对象,

.NET已提供了一种针对受限托管资源的处理模式--IDisposable模式。比较合理的设计就是派生一个新类型并实现IDisposable接口,在Dispose方法中将池化对象归还共享池(Pool)。

 public interface IPoolableObject:IDisposable
    {
        int Size { get; }
        void Reset();
        void SetPoolManager(PoolManager poolManager);
    }
public class PoolManager
    {
        private class Pool
        {
            public int PooledSize { get; set; }
            public int Count { get { return this.Stack.Count; } }

            public Stack<IPoolableObject> Stack { get; private set; }//泛型容器
            public Pool()
            {
                this.Stack = new Stack<IPoolableObject>();
            }

        }
        const int MaxSizePerType = 10 * (1 << 10);//10MB
        Dictionary<Type, Pool> pools = new Dictionary<Type, Pool>();
        public int TotalCount
        {
            get
            {
                int sum = 0;
                foreach (var pool in this.pools.Values)
                {
                    sum += pool.Count;
                }
                return sum;
            }
        }
        public T GetObject<T>()
            where T : class, IPoolableObject, new()
        {
            Pool pool;
            T valueToReturn = null;
            if(pools.TryGetValue(typeof(T),out pool))
            {
                if(pool.Stack.Count>0)
                {
                    valueToReturn = pool.Stack.Pop() as T;
                }
            }
            if(valueToReturn==null)
            {
                valueToReturn = new T();
            }
            valueToReturn.SetPoolManager(this);//对象池
            return valueToReturn;
        }
        public void ReturnObject<T>(T value) where T : class, IPoolableObject, new()
        {
            Pool pool;
            if (pools.TryGetValue(typeof(T), out pool))
            {
                pool = new Pool();
                pools[typeof(T)] = pool;
            }
            if(value.Size+pool.PooledSize<MaxSizePerType)
            {
                pool.PooledSize += value.Size;
                value.Reset();
                pool.Stack.Push(value);
            }
        }
        class MyObject:IPoolableObject
        {
            private PoolManager poolManager;
            public byte[] Data { get; set; }
            public int UsableLength { get; set; }

            public int Size
            {
                get { return Data!=null?Data.Length:0}
            }
            void IPoolableObject.Reset()
            {
                UsableLength = 0;
            }
            void IPoolableObject.SetPoolManager(PoolManager poolManager)
            {
                this.poolManager = poolManager;
            }
            public void Dispose()
            {
                this.poolManager.ReturnObject(this);
            }
        }
    }
View Code

被池化对象必须要实现自定义接口,需要有点工作量。为了实现池化和对象重用,必须完全了解并掌握这些对象。因为在每次把池化对象归还共享池时,你的代码必须把对象重置为已知的,安全的状态,

共享池中的对象永远不会被销毁,这与内存泄漏难以区分开。通常不要把池化作为默认解决方案,主要处理一些LOH分配。能消除99%的LOH问题。这类对象就是MemorySteam,我们用它来序列化并通过网络传递数据。

减少LOH的碎片整理

如果做不到完全避免LOH,就尽力避免碎片整理,

保证LOH的每次分配都是同一尺寸的,或者至少有几种标准尺寸的组合。比如LOH的一种常规用途就是缓冲区池,不能让各个缓冲区大小不易,而应该所有缓冲区都是相同尺寸,或者固定大小(1MB),如果某一块缓冲区确实需要被垃圾回收了,那么下一次分配的缓冲区就有很大概率会落在这块空闲内存上。而不会放到堆的末尾去。

 

 

posted @ 2020-03-27 22:49  梦一回  阅读(265)  评论(0编辑  收藏  举报