[翻译] 编写高性能 .NET 代码--第二章 GC -- 避免使用终结器,避免大对象,避免复制缓冲区

摘要:
注意,有些人认为必须执行终结器。避免大型物体。在分析了大量的程序代码之后,大型对象的边界被定义为85000字节。任何大于或等于此边界值的对象都将被确定为“大对象”,需要在单独的堆中分配。我们希望尽可能避免在大型对象堆上进行分配。为了避免这些问题,您需要严格控制程序在大型对象堆中的内容分配。避免复制缓冲区如果可能,应避免复制数据。
避免使用终结器

如果没有必要,是不需要实现一个终结器(Finalizer)。终结器的代码主要是让GC回收非托管资源用。它会在GC完成标记对象为可回收后,放入一个终结器队列里,在由另外一个线程执行队列里对象的终结器方法。这就意味着,如果你实现一个类的终结器,你必须保证在它在终结器执行后能被正常回收。这需要消耗一些CPU资源在清理对象上,会极大降低GC的整体效率。
如果你实现一个终结器,你也必须实现一个IDisposable接口用来清理资源,并在Dispose方法里调用GC.SupperessFinalize(this)将对象从终结器队列里移除。如果你在下次回收之前,正确的调用了Disspose方法,就不会让终结器执行。下面的栗子就是正确的演示了这个模式。

    class Foo : IDisposable
    {
        ~Foo()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.managedResource.Dispose();
            }

            // Cleanup unmanaged resourced
            UnsafeClose(this.handle);

            // If the base class is IDisposable object 
            // make sure you call:
            //base.Dispose(disposing);
        }
    }

你可以通过 [http://www.writinghighperf.net/go/15] 获得过的关于Dispose模式与终结器的更多信息。

注意 有些人认为终结器一定会被执行。正常情况下没错,但这不是绝对的。如果一个程序被强制终止,那么进程会立即消失,终结器自然也不会执行。当然主动退出时也许会有一个短暂的等待进程关闭时间,但如果你的终结器在终结器列表的后面,也是有可能不会被执行。此外由于终结器队列是循序执行,如果某个终结器进入了死循环,那么后面的终结器就不会被执行到。终结器不是执行在GC线程里,但他们会引发GC。

避免大对象

在分析了大量的程序代码后,大对象的边界被定义在85000 bytes上。任何大于等于这个边界值的对象都会判定为“大对象”,需要分配在一个单独的堆里。
我们希望尽可能的避免在大对象堆上进行分配。这不仅会导致更长的GC,也更容易造成内存碎片,让内存的分配边界随着时间不对增加。
为了避免这些问题,你需要严格控制程序在大对象堆里分配内容。你需要统筹安排你的对象在应用程序生存周期里的分配方案。
LOH是不会自动压缩的,但在.NET 4.5.1 之后,你还是可以通过特定方法去通知GC进行压缩。然而,这个是你最后的手段,因为这将导致一次很长的暂停。在做这之前,你还是好好想想,应该如何避免进入这个情况。

避免复制缓冲区

如果可能,你应该尽量避免复制数据。例如:如果你打算将一个文件数据读入MemoryStream(如果你需要一个大的缓冲区,最好做一个合并)。一旦分配了内存,每个组件都将从这个数据里的同一个副本里读取数据,并将其视为开发准则。

如果你只需要使用这个缓冲区里的一部分,可以使用ArraySegment类来访问这个缓冲区的部分数据。你可以使用 ArraySegment的Api来访问原始数据,你可以将它附加到一个创建的MemoryStram里。你所做的这一切不会产生新的数据副本。


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

复制内存最大的问题不在CPU而是GC。如果你发现要复制缓冲区,可以尝试将其复制或合并到一个现有的缓冲区里,以避免任何新的内存分配。

下一篇:第二章 GC -- 将长生命周期对象和大对象池化

免责声明:文章转载自《[翻译] 编写高性能 .NET 代码--第二章 GC -- 避免使用终结器,避免大对象,避免复制缓冲区》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Android Service 生命周期将excel文件的内容导入sql server数据库的方法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

索引节点(inode)爆满问题处理

后来,我用df-I检查/data分区的索引节点,发现它已满,这导致系统无法创建新的目录和文件。inode是用于存储这些数据的信息,包括文件大小、所有者、用户组、读写权限等。inode索引每个文件的信息,因此它具有inode的值。根据指令,操作系统可以通过inode值最快找到对应的文件。故障排除的原因是/data/cache目录中有大量小字节缓存文件,这些文件...

QMap与QHash

Qt提供两个主要的关联容器类:QMap和QHash。QMap的K和T有一对方便的函数keys()和values(),它们在处理小数据集时显的特别有用。QMap重载了value,返回一个给定键多有值的QList列表。在内部,它们都依赖于QHash,且都像QHash一样对K的类型有相同的要求。...

TFS(Team Foundation Server)简介和新手入门

随着VisualStudio产品线中TeamFoundationServer组件的公布,微软使得开发团队在僵化的软件project实践应用中取得了巨大进步。TeamFoundationServer起步TeamFoundationServer是这样一种server产品,它须要部署到软件开发环境中。利用Excel和project能够訪问存储在TeamFounda...

kernel: blk_update_request: I/O error, dev fd0, sector 0

检查后,控制台无法登录。重新启动虚拟机,报告下图,然后执行journalctl以显示以下系统消息日志原因搜索。。。...

支付宝支付api

使用:alipayDemo来配置支付宝支付接口1拿到商户号,回调地址,支付宝公钥,我的私钥---生成一个对象#给支付宝发请求,信息要用支付宝公钥加密#支付宝给我响应信息,信息会用商户的公钥加密,回来之后再拿用户私钥解密2对象.direct_pay传支付金额,支付商品描述,支付订单号---返回个加密的串3拿到加密的串拼到get请求参数部分pay_url="ht...

IDEA的设置打不开,点了没反应解决办法

把它去掉用回英文d就可以了。...