返回目录

第二章:垃圾回收

垃圾回收是你开发工作中要了解的最重要的事情。它是造成性能问题里最显著的原因,但只要你保持持续的关注(代码审查,监控数据)就可以很快修复这些问题。我这里说的“显著的原因”,实际上是我们对垃圾回收的理解和期望不正确导致的。在.NET开发中,内存的性能问题和CPU的性能问题一样多,这就是单独开一章主要描述这个问题的原因。

当我们提及垃圾回收造成的开销时,就会不如自主的紧张起来,但一旦你理解它,就能很好的优化你的程序。在后面文章里,你可以看到GC可以在大多数情况下,在堆处理上提供很好的性能,同时也能很好解决内存分配与内存碎片问题。

Windows在非托管堆里用使用一个空闲列表来维护内存分配。尽管它会想尽办法来减少内存碎片,但很多长时间运行的非托管代码(本地代码)的程序还是会碰上内存碎片问题。它会花很多时间在空闲列表里找到合适可分配地址。随着内存使用持续增长,不可避免的需要不断重启来解问题。一些程序还会采用自定义内存分配的方案(自己的内存分配算法来接管malloc)函数来解决内存碎片问题。

.NET里的内存分配通常在一个内存段里进行,这样申请和回收的消耗会小很多。托管堆通过将最近申请的内存对象放在一起,可以减少对空闲列表的遍历,提升性能。

在默认的分配过程中,通过代码获得对象的大小,然后在剩余的缓冲区里分配它。因为没有竞争,只要有合适的空间就能很快的分配。一旦这要申请的空间这一段无法满足,GC分配器会创建一个新的内存段,在这上面开始分配,之后的新的分配也都会在这新创建的内存段里进行。

这个过程中系统代码(分配器)只会做一些简单的检查。

我们来看一个简单的栗子:

private class MyObject
{
private int x;
private int y;
private int z;
} private static void Main(string[] args)
{
var x = new MyObject();
}

首先,我们在分配器前设置一个断点

; Copy method table pointer for the class into
; ecx as argument to new()
; You can use !dumpmt to examine this value.
mov ecx,3F3838h ; Call new
call 003e2100 ; Copy return value (address of object) into a register
mov edi,eax

目前分配的是:

; NOTE: Most code addresses removed for formatting reasons.
;
; Set eax to value 0x14, the size of the object to
; allocate, which comes from the method table
mov eax,dword ptr [ecx+4] ds:002b:003f383c=00000014 ; Put allocation buffer information into edx
mov edx,dword ptr fs:[0E30h] ; edx+40 contains the address of the next available byte
; for allocation. Add that value to the desired size.
add eax,dword ptr [edx+40h] ; Compare the intended allocation against the
; end of the allocation buffer.
cmp eax,dword ptr [edx+44h] ; If we spill over the allocation buffer,
; jump to the slow path
ja 003e211b ; update the pointer to the next free
; byte (0x14 bytes past old value)
mov dword ptr [edx+40h],eax ; Subtract the object size from the pointer to
; get to the start of the new obj
sub eax,dword ptr [ecx+4] ; Put the method table pointer into the
; first 4 bytes of the object.
; eax now points to new object
mov dword ptr [eax],ecx ; Return to caller
ret ; Slow Path – call into CLR method
003e211b jmp clr!JIT_New (71763534)

总之,这些涉及到方法调用的指令只有9个,不是很难懂。

如果你使用了一些配置选项,例如工作站模式,它不会因为竞争而导致分配变慢,因为GC给每一个处理器(cpu内核)分配了一个堆(段)。.NET在这些内存分配地方做了一些复杂处理,但你不必深入了解它是如何工作,只需要知道如何优化它就可以了。

我在本书开头就涉及到垃圾回收是因为今后章节里很多东西都会涉及到它。正确的理解垃圾回收是帮助你实现好性能的基础。

下一节 >> 基本操作

[翻译]编写高性能 .NET 代码 第二章:垃圾回收的更多相关文章

  1. [翻译]编写高性能 .NET 代码 第二章:垃圾回收 基本操作

    返回目录 基本操作 垃圾回收的算法细节还在不断完善中,性能还会有进一步的提升.下文介绍的内容在不同的.NET版本里会略有不同,但大方向是不会有变动的. 在.net进程里会管理2个类型的内存堆:托管和非 ...

  2. [翻译] 编写高性能 .NET 代码--第二章 GC -- 减少分配率, 最重要的规则,缩短对象的生命周期,减少对象层次的深度,减少对象之间的引用,避免钉住对象(Pinning)

    减少分配率 这个几乎不用解释,减少了内存的使用量,自然就减少GC回收时的压力,同时降低了内存碎片与CPU的使用量.你可以用一些方法来达到这一目的,但它可能会与其它设计相冲突. 你需要在设计对象时仔细检 ...

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

    避免使用终结器 如果没有必要,是不需要实现一个终结器(Finalizer).终结器的代码主要是让GC回收非托管资源用.它会在GC完成标记对象为可回收后,放入一个终结器队列里,在由另外一个线程执行队列里 ...

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

    将长生命周期对象和大对象池化 请记住最开始说的原则:对象要么立即回收要么一直存在.它们要么在0代被回收,要么在2代里一直存在.有些对象本质是静态的,生命周期从它们被创建开始,到程序停止才会结束.其它对 ...

  5. [翻译] 编写高性能 .NET 代码--第二章 GC -- 配置选项

    配置选项 在基于"less rope to hang yourself with"思想下,.NET 框架没有给开发提供很多太多的配置选项.但在大多数情况下,GC会跟你的硬件配置,及 ...

  6. [翻译] 编写高性能 .NET 代码--第二章 GC -- 减少大对象堆的碎片,在某些情况下强制执行完整GC,按需压缩大对象堆,在GC前收到消息通知,使用弱引用缓存对象

    减少大对象堆的碎片 如果不能完全避免大对象堆的分配,则要尽量避免碎片化. 对于LOH不小心就会有无限增长,但LOH使用的空闲列表机制可以减轻增长的影响.利用这个空闲列表,我们可以在两块分配区域中间找到 ...

  7. [翻译]编写高性能 .NET 代码 第一章:工具介绍 -- Performance Counters(性能计数器)

    <<返回目录 Performance Counters(性能计数器) 性能计数器是监视应用程序和系统性能的最简单的方法之一.它有几十个类别数百个计数器在,包括一些.net特有的计数器.要访 ...

  8. [翻译]编写高性能 .NET 代码 第一章:工具介绍 -- Visual Studio

    <<返回目录 Visual Studio vs虽然不是全宇宙唯一的IDE,但它是.net开发人员最常用的开发工具.它自带一个性能分析工具,你可以使用它来做开发,不同的vs版本在工具上会略有 ...

  9. [翻译]编写高性能 .NET 代码 第一章:性能测试与工具 -- 平均值 vs 百分比

    <<返回目录 平均值 vs 百分比 在考虑要性能测试的目标值时,我们需要考虑用什么统计口径.大多数人都会首选平均值,但在大多数情况下,这个正确的,但你也应该适当的考虑百分数.但你有可用性的 ...

随机推荐

  1. python-虎扑爬虫

    Python作为一个高级编程语言,不知从何时起就在圈子里流行起来了.个人也是图个鲜,跟上时代步伐学习了一下."鲁迅"说过:不能学以致用,就是耍流氓.我用python对虎扑论坛作了一 ...

  2. Windows挂钩注入DLL

    注入DLL实现源码:HINSTANCE g_hInstDll = NULL; HHOOK g_hHook = NULL; DWORD g_dwThreadId = 0; #ifdef _MANAGED ...

  3. form表单action=""的作用

    看项目时发现action="",可仍旧提交到后台相关页面了.查了一下,action=""相当于当前页面刷新,不过页面按照form表单提交参数到后台@参考文章

  4. Windows脚本修改主机名-不重启

    windows通过脚本方式修改主机名的方法有很多种,下面介绍修改注册表方式的脚本. 使用方法: 1 打开cmd,假如脚本名为ModifyHostname.bat 2 执行脚本,并加入脚本参数,其中第一 ...

  5. asp.net core 部署到服务器之后外网访问不了

    部署发现问题 今天在部署.net core的时候,发现访问http://localhost:xxxx可以,但是用外网访问并不行! 开始尝试解决问题 一开始以为是nginx的问题.各种折腾,各种改配置文 ...

  6. iOS页面切换动画实现方式。

    iOS页面切换动画实现方式. 1.使用UIView animateWithDuration:animations:completion方法 Java代码 [UIView animateWithDura ...

  7. mkdir -p 参数的使用

    ssh root@%s -o ConnectTimeout=2 "ssh root@%s ConnectTimeout=2 "if [ ! -d /root/scripts ]; ...

  8. c# 类属性和方法

    属性 public 类字段 就相当于c#里面暴露给外面的属性 类似nodejs的 module.exports 但是属性又不同于普通的字段,属性只是外部包装字段 没有自己的任何含量 类似退换后的方法. ...

  9. rapid framework开发系列(一)

    定义:web项目脚手架 rapid-framework是一个以spring为核心的项目脚手架(或者称为胶水框架),框架将各个零散的框架(struts,strust2,springmvc,hiberna ...

  10. vue中多个input绑定enter按键事件

    默认再每个input上加 keyup.enter 是可以绑定事件的 可是多个input一起绑定同一个事件就比较麻烦 所以就在vue dom里面遍历查找并绑定事件 let handleEnter = ( ...