[翻译] 编写高性能 .NET 代码--第二章 GC -- 将长生命周期对象和大对象池化

将长生命周期对象和大对象池化

请记住最开始说的原则:对象要么当即回收要么一直存在。它们要么在0代被回收,要么在2代里一直存在。有些对象本质是静态的,生命周期从它们被建立开始,到程序中止才会结束。其它对象显然不须要永远存在下去,但他们的生命周期会存在程序的某些上下文里。它们的存活时间会超过0代(1代)回收。这些类型的对象能够做为池化对象的备选。这虽然须要你手动管理内存,但实际状况下这是一个很好的选择。另一个重要的须要池化的对象是分配在LOH里的大对象。html

没有一个单一的标准方案或者API来实现对象的池化。 这须要你根据你的程序和对象的类型来设计对应方案。算法

对于如何管理池化对象,你能够将其当作非托管资源(内存)来进行管理。.NET对于这类资源有一个种管理模式:IDisposable。在本章前面咱们介绍了如何实现这种模式。一个比较合理的方式是实现IDisposable接口,在Dispose方法里将对象丢回对象池。
实现一个好的对象池策略并不简单,他取决于你程序要如何使用,以及那种类型的对象须要进行池化。
下面的栗子,实现了一个简单的对象池,你能够从里面知道对象池会涉及那些内容。这个代码能够从 PooledObjects 的栗子工程里看到。缓存

interface IPoolableObject : IDisposable
    {
        int Size { get; }
        void Reset();
        void SetPoolManager(PoolManager poolManager);
    }

    internal 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>();
            }
        }

        private const int MaxSizePerType = 10*(1 << 10); // 10 MB 

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

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

强制让每一个对象都实现接口会麻烦一些,但它除了方便外,还有一个重要的事实:为了使对象池重用对象,你必须能彻底理解并控制它们。每次对象回到对象池前,你的代码须要将对象从新设置到一个移植的,安全的状态。这意味着你不该该天真的直接用第三方的对象池组件。你须要设计接口,并让对象实现该接口,用来处理每一个对象获取时的初始化过程。你还须要特别当心对.NET框架对象作池化。安全

特别须要注意的是用来作对象池的集合,由于它们的性质决定--你并不但愿它们销毁所存储的数据(毕竟这是池的重点),但你须要一个能够表示能够为空和可用空间的集合。幸运的是,大多数集合类型都实现了长度和容量的参数。考虑到使用现有的.NET集合类型会存在风险,建议最好本身实现集合类型,并实现一些标准的集合接口(如:IList ,ICollection 等)。相关建立本身定义集合的内容,可参考本书第六章。另一个策略就是让你设计的可回收对象实现一个终结器(析构函数)。若是终结器运行,则意味着Dispose方法没有执行,这将会是一个小小的bug。你也能够在你的程序里一些地方记录日志,崩溃信息或者一些信号信息。 网络

请牢记,若是不清理对象池里的数据,这等同于内存泄漏。你的对象池应该有一个边界大小(不管是字节数量或者对象的数量),一旦超过,它应该通知GC清理多余的对象。理想状况下,你的对象池足够大,能够正常操做而不回收对象,但也会形成GC在执行回收时暂停时间变长,对象池里对象越多回收算法耗时也越多。固然最重要的仍是对象池能知足你的须要。框架

我一般不会将对象池做为默认的解决方案。它做为一种通用机制,显得很笨重以及容易出错。但你可能会发现你的程序在某些类型上很适用对象池。在一个应用里分配了大量的LOH对象,咱们调查后发现,能够将一个单一的对象池化就能解决99%的问题。这个就是MemoryStream,咱们使用它来序列化网络传输数据。实际的实现不单单是将构建了一个MemoryStream的队列,由于要避免内存碎片,有一些更复杂的设计,但从本质上来讲仍是将它池化。每次使用完MemoryStream对象,它都会被放入对象池里。函数

下一篇:第二章 GC -- 减小大对象堆的碎片,在某些状况下强制执行完整GC,按需压缩大对象堆,在GC前收到消息通知,使用弱引用缓存对象this

相关文章
相关标签/搜索