CLR 完全介绍
审核用于.NET 应用程序的内存使用量
Subramanian Ramaswamy and Vance Morrison
内容
当内存使用量影响速度
内容可以被完成?
任务管理器
共享与非共享的内存
应用程序大小
VADump: 更多详细的视图
.NET 垃圾回收器
PerfMon
结论
性能优化是将一件事情: 使运行得更快的计算机程序。成本个指令操作数提取数据时,说明是执行时从现代计算机硬件。因此,内存使用情况可如何快速应用程序上有直接影响执行,而且是要优化的重要指标。在这篇文章,我们将讨论.NET 程序的内存优化的基本知识。首先,概述的情况下内存访问是一个瓶颈,并且用于优化。接下来,我们将讨论如何将内存使用典型的.NET 程序中的常规细分结构。最后,我们讨论工具和策略确定您的.NET 应用程序内存消耗,并减小。
当内存使用量影响速度
第一个大小写内存消耗相关的问题是一个大量占用 CPU 的应用程序,在处理大量数据的。在典型的 PC 可以执行少于半一个 nanosecond 中的指令 (0.5 ns)。但是的速度受到的时间来从内存中提取操作数。现代处理器有缓存的层次结构来优化硬件的成本。1 级 (L 1) 缓存,最快的内存,但是相对较小。接下来在层次结构是级 2 高速缓存跟在主的内存 (RAM),和最后硬盘驱动器。图 1 显示为典型的 PC 内存层次结构的各个部分的大小和访问时间。在每个步骤更深入的内存层次结构,访问时间 (和大小) 增加一个数量级或多个 (硬盘驱动器是低于 RAM 的 10,000 个时间) 时 (每字节) 成本降低。
图 1 的大小和与非本地存储的访问时间 |
L 1 缓存 | L 2 缓存 | 内存 (RAM) | 磁盘 | |
大小 | 64K | 512K | 2000M | 100,000M |
访问时间 | .4 ns | 4 ns | 40 ns | 4,000,000 ns |
如果热数据路径访问更多的内存,然后操作数经常需要获取从速度较慢的内存。因为较慢的内存是较慢的一个数量级,几级 2 缓存未命中可以有显著的性能影响。
第二个的情况 (某些) 内存消耗相关问题的将是应用程序的冷启动期间中。如 图 1 所示的硬盘访问将是比主内存访问更慢。操作系统尝试缓解这通过缓存从主内存中磁盘的数据。这是应用程序时更快地启动第二次在称为热启动 (数据时更快的内存中缓存的) 的原因。为在第一个的 (冷启动缓存不还发生了,并且数据必须从磁盘提取。唯一的方法提高这是从磁盘中加载较少的数据。只从磁盘 (如程序指令中) 获取的内存影响冷启动,; 由程序本身,包括堆和堆栈上的所有数据初始化的内存不会影响冷启动。
最后一情形在应用程序切换内存消耗问题时。应用程序是相当大 (大小大于 50MB) 和一个用户切换到其他应用程序,这些应用程序窃取您的应用程序的物理内存。当用户返回到您的应用程序中时,这些被盗的页需要获取从该磁盘使应用程序非常慢。这与类似于冷启动情况之处在于它影响而不仅仅是程序指令,但所有内存,其中包括您的应用程序已初始化的内存。由于服务器在不断地同时运行许多不相关的程序,服务器是不断地切换的应用程序,; 这意味着该内存几乎始终是服务器问题。
内容可以被完成?
如果重新代码可能 magically 排列以确保快速缓存已满足所有的内存请求,程序会加快明显。实际上,这是唯一可能在特殊情况下,因为该程序的算法通常指示内存访问的顺序。一个更可行的技术是内存的最小化使用量。这将减少快速缓存负担,并使该程序速度更快。数据有经常访问的结构 (热) 部件 CPU 缓存中无法容纳该执行 (通常将大于几兆字节),热数据的内存大小 30%减少通常导致 CPU 速度在 10%改进。
可以采用三种方式减少内存。首先,您可以执行较少帮助冷启动) 的代码。这适用于其中的内容计算 inefficiently 首先明显的情况下。第二个,您可以按较少的数据。这是类似于在第一个的策略,但应用于所涉及该数据结构。最后 (和可能是最常用),可以使得较小,以不同方式编码数据结构或者通过将物理上分离 (小) 通常访问数据 (大) uncommonly 访问部分中。
这些技术通常需要数据的表示形式的更改并需要大量的代码网站,以实施的更改。因此,还会更更便于在这些早期中更改在开发周期以便其支付早期考虑内存 !
任务管理器
减少您的应用程序内存消耗在第一步是了解多少它正在使用。为此,您可以使用 Windows 内置任务管理器应用程序。
大多数用户都已熟悉使用任务管理器。通过在运行命令窗口 (Winkey + R) 中, 键入 taskmgr 或按 Ctrl + Alt + Del 并选择"启动任务管理器",您可以调用它。在"进程选项卡上您会发现所有系统的当前正在运行的进程的信息。如果列不包含 PID,内存工作集和内存专用的工作集使用视图 | 选择将它们添加到显示的列的菜单选项。
共享与非共享的内存
工作集指当前正在使用由过程物理内存。但是,操作系统执行以确保所有内存都是平均成本的优化。大部分一个进程使用的内存包含只读数据 (是例如实际指令执行)。因为此数据是只读的它可以由需要的所有进程共享。因为所有进程都进行充分利用共享、 只读的操作系统代码,共享大量每个进程工作集。因此,总的工作集会显著 overestimate 进程使用的内存的真正的成本。
操作系统还跟踪的非共享行 (专用) 的内存。这包括由进程使用的所有读写内存。私人工作集 underestimates True 成本由进程使用的内存时 (我们将看到如何时我们将讨论该工具 VADump),它会是更好的跃点数来优化,因为不同优化共享的内存,专用内存中的任何收益将减少在计算机上的总内存压力。
最后,总和专用内存计数错过由进程使用的重要内存: 文件系统缓存。因此开销大硬盘的访问是直接在内存,未映射文件的数据时其缓存由操作系统。此内存使用增加在的系统中的内存压力,并且不包含在的工作集指标 (它属于操作系统) 中。没有很多有关文件的访问进行 (如果您的程序需要文件,它不能避免),使它可以被视为不能进行优化的成本。
应用程序大小
应用程序可能会被分为小、 中等,或大根据其内存使用情况。一个小应用程序具有 20MB 或设置与较小的大小较小的工作比 5MB 私人工作集 ; 在中的应用程序设置大约 20 MB 私人工作集大小大约 50MB 的正常工作 ; 大型应用程序通常具有工作集大小大于 100 MB,私人工作集大小超过 50MB。大您的应用程序更多的帮助优化应用程序的内存使用情况很可能。
一种简单而快速的方法,来监视内存使用和查找泄漏是您的应用程序上运行探测测试。运行一段应用程序并监视其工作集使用情况 ; 如果工作集增长无限,可以意味着内存泄漏或其他问题。
VADump: 更多详细的视图
任务管理器提供了仅应用程序的内存使用率的摘要。要获取更多详细信息,您需要一个名为 VADump 的工具 (请参阅资源提要栏)。这会调用在其中安装 VADump 目录下命令提示符下键入 VADump –sop ProcessID。打印明细信息的单一进程下到粒度 DLL 级别中的内存。典型的转储如 图 2 所示。
图 2 在记事本中打开的 VADump 输出
若要读取转储,开头总和总的工作集。此数目应同意数在任务管理器。此数字然后已划分为八个类别。这些类别最有趣的是:
- 代码 / 静态数据表示由进程加载的 DLL。
- 堆的代表本机 (不 GC 堆使用的内存。
- 其他数据,表示分配使用 OS VirtualAlloc 函数的内存。对于托管代码这很重要,因为它包含整个垃圾回收堆。
性能资源
VA 转储:
go.microsoft.com/fwlink/?LinkId=149683
CLR 性能团队博客 (指导调查可疑的 DLL 加载):
VS 事件探查器团队博客:
提高.NET 应用程序性能和可伸缩性:
msdn.microsoft.com/library/ms998530
Windows 性能日志: 使用 Xperf 的调查:
Vance Morrison 博客:
波多黎各 Mariani 博客:
检查代码的 Lutz Roeder 的.NET Reflector:
CLR 全面透彻的研究内存问题:
msdn.microsoft.com/Magazine/cc163528
使用的 DLL,内存将进一步按分解 VADump 摘要表后。每个 DLL,它显示数量的页面 (页面是始终 4K) 每个 DLL 使用。因此,一个可以确定加载的所有代码的内存开销。
在 图 2 ,没有行标记为"总计汇总工作设置。 工作集 (KB) 和页中的总处于第一列。列 2、 3 和 4 (专用 KB、 共享 KB 和 SharedKBytes) 添加到工作集列的总数。是列 2,专用 KB 值,而第 1 列显示为总的工作集 TaskManager TaskManager 中的私人工作设置中描述的。因此,VADump 允许您查看专用和总的工作集,包括共享和共享的工作集分隔。这是比什么是可通过 TaskManager 更完整的图片。
大.NET 应用程序时它们是通常较大或者因为运行大量代码或在使用大量数据。
在这种情况下您将看到大量的加载的 DLL,并且代码 / 静态数据大小往往 dominate 工作集的总数。用于托管的应用程序此数据保持在 GC 堆,并且因此显示为 dominating 工作集的其他数据。
在 图 2 在下半部分,可以看到工作集页面) 中的列出的模块。这会告诉您哪些模块导致工作集应用程序和多少工作集的每个模块正在使用。因此,可以非常快速地确定特定的 DLL 所占的 DLL 的专用、 共享,和共享的工作集多少工作集。此视图中明确地显示是否可以消除 DLL 加载,和多少字节的私人工作集可以 shaved 关闭应用程序的工作集。
一次的标识播放可能不是付薪的 DLL (是例如 DLL 可能会加载即使不使用特定的执行中) 下, 一步是确定加载特定 DLL 的原因,并设法消除不必要的负载。步骤的研究可疑的 DLL 加载可在该CLR 和 Framework 性能日志.
堆数据 VADump 输出所显示的是为非托管堆,这是不会被.NET 垃圾收集器管理内存。值得保留此数小,使垃圾收集器可以通过清除根据需要管理大部分内存。
其他数据类别表示调用一个基元操作系统内存分配函数 (VirtualAlloc) VADump 不能以任何其他方式进行分类的。对于.NET 的应用程序通常的其他数据的最重要的组件是垃圾回收堆包含所有用户定义的对象。
.NET 垃圾回收器
.NET 运行库支持自动内存管理。它跟踪每个由托管程序的内存分配,并定期调用发现不再使用,就会重用新分配的内存的 GC。一个重要的优化,垃圾回收器执行的是它不搜索整个堆每次,但分区为三个版本 (0、 1 和 2) 的堆。
最小的生成 0,通常需要仅 1 / 10 的要清除的最后一个 GC 之后发生 (和显然,未使用) 在分配的完成,但仅查找的毫秒。理想情况下,一代的大小小于 L 2 缓存大小。生成 1 GC 解决 survived 一个 GC 的分配 ; 花费较长时间运行比第级花大约 1 毫秒的 0 GC。理想情况下,应该有 10 个第 0 代 GC,为每个第 1 代垃圾收集器。
第 2 代 GC 处理所有对象。因此,花费的时间可以是重要的。是例如可能需要大约 160 个毫秒的时间明显金额 20MB 堆。时间增长大致线性堆 (大约 8 毫秒每为非常粗略的估计的 MB) 的大小。True 的成本取决于继续存在中继续存在内存,和如何碎片堆是的 GC 指针的数量的内存量。理想情况下,应该有 10 个第 1 代 GC,为每个第 2 代垃圾收集器。
执行完整地,.NET GC 堆类似一个 sawtooth 与对应于第级 2 的收藏集,如 图 3 所示的 troughs。典型第 2 级堆 trough 比率为大约 1.6,与在很大程度上无关堆大小 (与不碎片中) 之比。碎片的存在此号码因显著。
图 3 GC 堆 Sawtooth Waveform
性能监视器
VADump 提供进程中的内存使用情况的细分结构的第一个级别。但是,它不会不准确告诉我们我们使用的 GC 内存 (其他数据类别可以包括 GC 堆以外的内存,),它不会告诉我们的 GC 生成正常比率。为此,我们需要使用 Windows PerfMon 应用程序。可以通过在应显示在 图 4 所示的窗口的运行命令窗口中键入 PerfMon 启动它。PerfMon 可以收集大量的性能数据,但此处我们关注中用于监视 GC 堆。
图 4 PerfMon 启动屏幕
图 5 选择 PerfMon 中监视的计数器
PerfMon 出现后,我们需要将其配置为显示垃圾收集器的信息。我们通过第一个单击性能监视器项,在左窗格中的树控件中完成此操作。这将更改右窗格显示性能计数器数据。现在,单击在 + 注册添加新的计数器。下一步,选择您想要想要观看如 图 5 所示的进程以及监视的计数器。
当您在中选择几个计数器您将注意到使用运行库的所有应用程序的名称。您可以选择要监视的一个或两个或但很多应用程序。此外,实例命名所有实例是在显示的所有实例能够监视数据但该数据将单独显示。此外,有是从不同的实例数据求和 _Global_ 实例。
如果 PerfMon 正在使用已监视其他应用程序后,已启动的应用程序,一个可以通过单击添加多个应用程序,并登录,并添加为新的应用程序的计数器 (只添加新的实例是必需的 ; 其他实例将继续显示 PerfMon 中)。
最后,默认情况下将数据以图形方式,显示,但是更有用以数字显示它。这可以通过单击报告类型工具栏 ( 图 6 ) 上。在一个测试,它显示我们的 7.3MB 超出总私人工作集的 8.6MB 是占用由 GC 堆,并在垃圾收集器花费了大约 11 的时间的百分比。在垃圾收集器中次良好数字的少于 10%总的应用程序的时间这样此特定应用程序将在 borderline 上。最后,它还告诉我们数量的第 0 代、 第 1 代和第 2 代的集合。理想情况下,我们需要的第 0 代收集为至少 10 次第 1 代收集的数量和为至少 10 次第 2 代收集的第 1 代收集的数量。
图 6 PerfMon 报表显示
结论
内存问题是众所周知难进行调试的。如果您的应用程序是足以关心内存的密钥是在开发的早期阶段限制内存使用情况。了解如何应用程序内存划分是此进程中的第一步,; 监视应用程序内存使用量是下一个。按照的与用于标识通过提出有关哪些 DLL 影响最大到内存消耗,原因第 2 代 GC 那么频繁发生,是的内存分配支付播放,等问题的机会。然后相应地优化内存使用情况。如果立即采取只有一个课程,应的早期花费在开发周期的内存问题,时间支付为自己以后,以便真正支付早期考虑内存 !
将您的问题和提出的意见发送至clrinout@Microsoft.com.
Subramanian Ramaswamy 是 Microsoft 的 CLR 性能,项目经理。他包含一个还是在电源和计算机从该 Georgia 工程 Institute of Technology。
Vance Morrison 是合作伙伴体系结构和组管理器在 Microsoft 的 CLR 性能。他推动.NET 中间语言 (IL) 的设计,他已经涉及使用.NET 自其开始。