.NET 最佳实践 No: 1:- 检测 .NET 代码中高内存消耗函数






4.81/5 (42投票s)
.NET 最佳实践 No: 1:- 检测 .NET 代码中高内存消耗函数
.NET 最佳实践 No: 1:- 检测 .NET 代码中高内存消耗函数
引言和目标
不要在生产环境中使用 CLR,也不要将其作为性能评估的起始工具 结论. 其他文章 | 使用 finalize/dispose 模式提高垃圾回收器性能![]() |
引言和目标
.NET 代码中性能下降的重要因素之一是内存消耗。许多开发人员只关注执行时间来确定 .NET 应用程序中的性能瓶颈。仅仅测量执行时间并不能清楚地说明性能问题所在。好的,说完了,最大的任务之一是了解哪个函数、程序集或类消耗了多少内存。在本教程中,我们将看到如何找到哪些函数消耗了多少内存。本文讨论了使用 CLR 分析器研究内存分配的最佳实践。
非常感谢 Peter Sollich 先生
让我们首先感谢 Peter Sollich,他是 CLR 性能架构师,编写了如此详细的 CLR 分析器帮助文档。安装 CLR 分析器后,不要忘记阅读 Peter Sollich 编写的详细帮助文档。
非常感谢先生,如果您访问我的文章,请告诉我您的意见。
CLR 分析器来帮忙
CLR 分析器是一种工具,可以帮助检测 .NET 代码中内存分配的发生方式。CLR 分析器工具由 Microsoft 提供,您可以从以下网址下载:
http://www.microsoft.com/downloads/details.aspx?familyid=A362781C-3870-43BE-8926-862B40AA0CD0&displaylang=en .
注意:- CLR 分析器有两个版本,一个用于 .NET 1.1,另一个用于 2.0 框架。对于 2.0 CLR 分析器,您可以访问 http://www.microsoft.com/downloads/details.aspx?familyid=A362781C-3870-43BE-8926-862B40AA0CD0&displaylang=en ,要下载 1.1,您可以使用 http://www.microsoft.com/downloads/details.aspx?familyid=86ce6052-d7f4-4aeb-9b7a-94635beebdda&displaylang=en#Overview
下载 CLR 分析器后,您可以解压缩并从 bin 文件夹运行“CLRProfiler.exe”。
如果您下载了 2.0 CLR 分析器,它会为“X86”和“X64”环境提供可执行文件。因此,请确保运行适当的版本。
CLR 分析器的特点
CLR 分析器是了解 .NET 应用程序代码中内存分配方式的最佳工具。它执行两个主要重要功能:-
• 提供有关 .NET 应用程序中内存分配方式的完整报告。因此,您可以查看按数据类型、函数、方法等分配内存的完整报告。
• 它还提供方法被调用的次数。
不要在生产环境中使用 CLR,也不要将其作为性能评估的起始工具
CLR 是一种侵入性工具。换句话说,它运行自己的逻辑来转储应用程序中每个函数/类/模块的内存值。换句话说,它干扰了应用程序逻辑。假设您有一个正常的应用程序,它调用函数 1 和函数 2。当您使用 CLR 分析器分析您的应用程序时,它会在每次函数调用后注入内存堆数据转储,如下所示。
换句话说,不要使用 CLR 分析器来查找应用程序的执行时间。它实际上会使您的应用程序慢 10 到 100 倍。您最终会得到错误的结果。
如前所述,因为它是一种侵入性工具,所以您绝不应在生产环境中使用它。
您不应该首先使用 CLR 分析器工具来分析您的性能问题。它更多的是第二步活动,您已经确定了存在内存问题的函数或类。因此,您可以使用性能计数器来首先找到哪些方法和函数需要很长的执行时间,然后使用 CLR 分析器来查看内存分配是如何完成的。
我们如何运行 CLR 分析器?
一旦您从 Microsoft 站点下载了 CLR 分析器,将文件解压缩到一个文件夹中。转到 Binaries 中的解压缩文件夹 -> 选择您的处理器并运行“CLRProfiler.exe”。您将看到 CLR 分析器,如下图所示。
第一步我们需要决定要分析什么。有两件事,一个是内存分配,另一个是方法调用次数。因此,选择您要分析的数据,然后单击“启动应用程序”。
完成后,您可以看到完整的分析摘要,如下图所示。这是一个非常复杂的报告,当我们分析示例应用程序时,我们将看到一种简单的方法。
CLR 分析器面临的问题
我们运行 CLR 分析器时遇到的一些问题。如果您看到下面的屏幕并且它没有停止,可能有两个原因:-
• 您有 .NET 2.0,并且您正在运行 CLR 分析器 1.1。
• 您尚未在 GAC 中注册 ProfilerOBJ.dll。
我们将要分析的示例应用程序
我们将要分析的应用程序非常简单。它有一个简单的按钮,可以调用两个函数“UseSimpleStrings”和“UseStringBuilders”。这两个函数都连接字符串。一个使用“+”进行连接,另一个使用“StringBuilder”类。我们将连接字符串 1000 次。
private void UsingSimpleStrings() { string strSimpleStrings=""; for (int i = 0; i < 1000; i++) { strSimpleStrings = strSimpleStrings + "Test"; }}
使用“StringBuilder”类进行连接的函数。
private void UsingStringBuilders() { StringBuilder strBuilder = new StringBuilder(); for (int i = 0; i < 1000; i++) { strBuilder.Append("Test"); } }
这两个函数都通过按钮单击调用。
private void btnDoProfiling_Click(object sender, EventArgs e) { UsingSimpleStrings(); UsingStringBuilders(); }
使用 CLR 分析器分析我们的示例
现在我们了解了我们的应用程序,我们将尝试使用分析器来查看哪个函数使用了多少内存。因此,单击“启动应用程序”-> 浏览到应用程序 exe,然后单击“检查内存分配”按钮并关闭应用程序。您将弹出一个完整的摘要对话框。
如果您单击直方图按钮,您可以看到按数据类型划分的内存分配。我明白这非常令人困惑。所以目前先放着。
如果您有兴趣查看按函数分配了多少内存,您可以单击“分配图”。这将显示每个函数消耗了多少内存。即使此报告也非常令人困惑,因为有太多的函数,太多的方法,我们无法找到我们的两个字符串函数“UsingStringBuilders”和“UsingSimpleStrings”。
为了简化上面的图表,右键单击,您将弹出许多过滤选项。让我们使用“查找例程”搜索来过滤掉不必要的数据。我们已经输入以获取按钮单击事件。从这个按钮单击调用了这两个函数。
现在搜索放大了方法,如下图所示。现在双击如下图中突出显示的“btnDoProfiling_Click”框。
双击后,您将看到以下详细信息。现在好多了。但是第二个函数去哪儿了?它只显示“UseSimpleStrings”函数。这是因为报告很粗糙。所以点击“所有”,您应该会看到所有函数。
您现在也可以看到另一个函数。26 字节是什么?。它只是在调用函数时需要的额外字符串操作,所以我们可以忽略它。让我们专注于我们的两个函数“UseSimpleStrings”和“UseStringBuilders”现在可以看到。您可以看到字符串连接占用 3.8 MB,而 StringBuilder 对象仅占用 16 KB。因此,StringBuilder 对象比简单的字符串连接消耗更少的内存。
那是一种困难的方式,有没有简单的方式?
上面显示的方法确实很困难。假设您有 1000 个函数,并且您想分析哪个函数消耗了多少内存。实际上,不可能遍历每个调用图,进行查找并获取您的函数。
最好的方法是将详细报告导出到 Excel 中,然后分析数据。因此,单击“视图”->“调用树”。
单击调用树后,您将看到如下图所示的内容。单击“视图”->“所有函数”-> 您将看到所有函数 -> 单击“文件”并将其另存为 CSV。
将完整数据导出到 CSV 后,您可以轻松找到您的方法和函数,并查看已分配了多少数据。
使用注释简化结果
如果您知道要分析哪些方法,您可以在调用方法时启用分析器。换句话说,我们可以从应用程序中启用 CLR 分析器。
为了从 C# 代码中启用分析器,我们首先需要引用“CLRProfilerControl.dll”。您可以从我们有分析器 exe 的同一文件夹中获取此 DLL。
然后,您可以直接从代码中调用分析器控件,如以下代码片段所示。我们在调用两个字符串操作函数之前启用了分析器。函数调用完成后,我们已禁用分析器。
private void btnDoProfiling_Click(object sender, EventArgs e) { CLRProfilerControl.LogWriteLine("Entering loop"); CLRProfilerControl.AllocationLoggingActive = true; CLRProfilerControl.CallLoggingActive = true; UsingSimpleStrings(); UsingStringBuilders(); CLRProfilerControl.AllocationLoggingActive = false; CLRProfilerControl.CallLoggingActive = false; CLRProfilerControl.LogWriteLine("Exiting loop"); CLRProfilerControl.DumpHeap(); }
所以现在让我们运行 CLR 分析器并启动我们的应用程序。请确保您禁用“启用分析”复选框,因为我们将从代码本身启用分析。
现在,如果您查看直方图,您将看到有限的数据。您可以看到它只记录了“System.String”和“System.Text.StringBuilder”数据类型消耗的内存分配。
如果您查看分配图,您可以看到它现在非常整洁。杂乱的视图已完全消失,我们有了非常简单和集中的视图。请注意单击“所有”以查看其余函数。
如前所述,不要过分关注执行时间
在摘要页面上,您可以看到注释按钮。如果您单击注释按钮,它会显示开始时间和结束时间。不要被记录的时间所迷惑。正如我们之前澄清的那样,它是一种侵入性工具,结果不准确。
Entering loop (1.987 secs) Exiting loop (2.022 secs)
结论
• CLR 分析器可用于查找分配给函数、类和程序集的内存,以评估性能。
• 不应在生产环境中使用。
• 不应将其用作性能评估的起点。我们可以首先运行性能计数器,获取执行时间长的方法,然后使用 CLR 分析器查看实际原因。
• 您可以使用直方图查看内存分配,使用调用图查看按方法划分的内存分配。
• 如果您知道要分析哪些方法,您可以从应用程序本身启用分析器。
总而言之,当您想查看内存分配时,没有任何工具能比 CLR 分析器更好。
源代码
您可以在此处找到用于分析的示例源代码
其他文章
有关最佳实践第 2 部分,请单击此处
有关最佳实践第 3 部分,请单击此处
有关最佳实践第 4 部分,请单击此处
有关最佳实践第 5 部分,请单击此处
我的 FAQ 文章
我确实明白这不是谈论我的常见问题解答的正确文章。只是想拍拍自己,完成了我的常见问题解答系列一年。以下是所有内容的汇总链接:-
Silverlight 常见问题解答:- https://codeproject.org.cn/KB/WPF/WPFSilverLight.aspx
LINQ 常见问题解答:- https://codeproject.org.cn/KB/linq/LINQNewbie.aspx
WWF 常见问题解答:- https://codeproject.org.cn/KB/WF/WWF.aspx
WCF 常见问题解答:- WCF.aspx
Sharepoint 常见问题解答:- SharePoint.aspx
Ajax 常见问题解答:- AjaxQuickFAQ.aspx
架构常见问题解答:- SoftArchInter1.aspx
本地化和全球化:- LocalizationGlobalizPart1.aspx
项目管理常见问题解答:- ProjectManagementFAQ.aspx
如需进一步阅读,请观看以下面试准备视频和分步视频系列。