.net 最佳实践一:监测.net代码中的高内存消耗函数<转

简介:




.net 最佳实践一:监测.net代码中的高内存消耗函数

简介和目标

导致.NET 代码性能下降的重要因素之一是内存消耗。 许多开发人员只是主要用执行时间来确定.NET 应用程序的性能瓶颈。 只测量执行时间并不清楚知道性能问题之所在。 好,要说的和要做的一个最大任务就是知道哪些函数、 程序集或类占用了多少内存。 在本教程中,我们将看到我们如何找出哪些函数消耗多少内存。 本文讨论的最佳实践涉及使用 CLR 探查器(CLR profiler)研究内存分配。

请随时到 http://www.questpond.com下载我的涵盖.NET ASP.NET SQLServer WCFWPFWWF的免费500个问题和回答的电子书。

 

非常感谢Peter Sollich先生

在开始本文时,首先要感谢CLR性能架构师Peter Sollich先生,他给CLR 探查器写了详细的帮助。当你安装CLR 探查器时不要忘记阅读Peter Sollich先生写的详细的帮助文档。

 

CLR Profiler to rescue

CLR探查器是一个帮助我们对.net代码监测内存分配情况的工具。CLR探查器由微软提供,你可以从下面的网址下载:

http://www.microsoft.com/downloads/details.aspx?familyid=A362781C-3870-43BE-8926-862B40AA0CD0&displaylang=en

注意:CLR探查器有针对.net 1.12.0框架的两个版本。2.0框架的版本在http://www.microsoft.com/downloads/details.aspx?familyid=A362781C-3870-43BE-8926-862B40AA0CD0&displaylang=en1.1的版本在http://www.microsoft.com/downloads/details.aspx?familyid=86ce6052-d7f4-4aeb-9b7a-94635beebdda&displaylang=en#Overview

下载并解压CLR探查器之后,可以从bin目录下运行CLRProfiler.exe

如果你下载的是2.0版本的CLR探查器,它提供针对X86X64的两种执行环境,清确认你运行了正确的版本。

 

CLR探查器特性

如果你企图了解.net应用程序代码是如何 进行内存分配的,CLR探查器是最好的工具。它有两个重要的功能:

  • <!--[if !supportLists]--> <!--[endif]-->给出一个.net应用程序是如何进行内存分配的报告。这样你就可以看到一个关于每一种数据类型,函数,和方法等是如何进行内存分配的完整报告。
  • <!--[if !supportLists]--> <!--[endif]-->它同时也提供调用方法耗费的时间。

<!--[if !vml]--><!--[endif]-->

不应在产品中使用CLR探查器作为性能评估工具

CLR探查器是一嵌入工具,换句话说,它为应用程序内的每一个函数、类、模块在内存使用方面执行它自己的逻辑。比如说你有一个应用程序调用了函数1和函数2,当你使用CLR探查器探测应用程序时,它在每一个函数调用之后插入关于内存堆数据的使用情况,如下

<!--[if !vml]--><!--[endif]-->

换句话说,不要使用CLR探查器来查看你的应用程序的执行时间,它会减慢你的应用程序10100倍,你会因错误的结果而死去。

如上所说,它是一个侵入式工具,你永远不要在你的产品环境中使用它。

第一,你永远不要一开始就用CLR探查器来分析你的性能问题。它更多的是用在第二步,在这里你可以调整一个有内存问题的函数或类。更可能的情况是首先使用性能计数器找到哪一个方法或函数的执行用了比较长的时间,然后再使用CLR探查器查看内存分配情况是怎么样的。

怎么运行CLR探查器

当你从微软网站下载CLR探查器后,把文件解压到一个目录中。转到解压后的目录下的Binaries目录,选择并运行‘CLRProfiler.exe’ CLR探查器运行如下图所示。

第一步要确定我们想探查什么,有两个探查选项,一是内存分配,另一是调用方法的次数,然后选择你要探查的数据,点击开始应用程序。

<!--[if !vml]--><!--[endif]-->

当完成之后,你会看到如下所示的探查结果的完整摘要。那是一个复杂的报告,当我们探查一个简单的应用程序时,我们想看到一个简化的结果。

<!--[if !vml]--><!--[endif]-->

 

使用CLR探查器面临的问题

运行CLR探查器时我们面临一些问题。如果我们看到了下面的界面,但它并没有结束,可能有两个原因:

  • <!--[if !supportLists]--> <!--[endif]-->你的环境是.net 2.0,但你运行的CLR探查器是1.1的。
  • <!--[if !supportLists]--> <!--[endif]-->你没有在GAC中注册ProfilerOBJ.dll

<!--[if !vml]--><!--[endif]-->

 

我们要探查简单的应用程序

我们要探查的应用程序非常的简单,它有一简单的按钮,它调用函数‘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();

}
  

<!--[if !vml]--><!--[endif]-->

 

使用CLR探查器探查我们的简单程序

现在我们了解我们的应用程序,我们尝试使用CLR探查器探查哪一个函数使用了多少内存。然后点击开始应用程序,找到我们的应用程序的exe文件,选上内存分配复选按钮,并关闭应用程序。现在会弹出一个完整的摘要对话框。

点击显示柱状图按钮,你会看到每种数据类型内存分配情况。我知道这非常容易搞混,所以保留了这个截图。

<!--[if !vml]-->  

如果你对每一个函数分配了多少内存感兴趣,点击‘Allocation Graph’,它给出每一个函数消耗了多少内存。因为有很多函数,很多方法,所以这个报告常常被搞混,并且我们不能我们的两个函数‘UsingStringBuilders’  ‘UsingSimpleStrings’字符串。

<!--[if !vml]--><!--[endif]-->

为了简化上面的图形,右击会弹出一些过滤选项,我们使用“查找过程”(‘Find Routine’)搜索过滤掉不需要的数据,我们键入按钮的点击事件名称,从点击事件调用了两个函数。

<!--[if !vml]--><!--[endif]-->

探索结果放大到如下图所示的方法上,在如下高亮显示的‘btnDoProfiling_Click’框上双击:

<!--[if !vml]--><!--[endif]-->

双击后我们会看到下面的细节,它现在更好一些。但是第二个函数消失到哪了呢。它只显示出了‘UseSimpleStrings’函数。这是因为这个报告是粗略的,在“所有”(0(everything))上点击你就会看到所有的函数。

<!--[if !vml]--><!--[endif]-->

现在可以看到另一个函数了。26个字节是什么?那是当函数被调用时额外的字符串操作所必须的,所以我们可以不再会它。我们把注意力集中到现在都可以看到的两个函数‘UseSimpleStrings’ ‘UseStringBuilders’上。你能看到字符串连接消耗了3.8MB,而使用StringBuilder对象只消耗了16KB。所以使用StringBuilder对象比使用简单字符串连接消耗更少的内存。

<!--[if !vml]--><!--[endif]-->

 

That was a tough way any easy way(不知道是什么意思)

上面所示的方式感到很吃力,假如你有1000个函数,你想分析哪一个函数消耗了多少内存,事实上是不可能通过查看每一个调用图,来找到你的函数。

最好的方法就是把报告导出到excel中然后再分析,点击‘view’菜单下的‘call tree’。

<!--[if !vml]--><!--[endif]-->

当你点击‘call tree’之后,就显示如下所示的内容,再点‘view’-‘all function’,就能看到所有的函数,再点文件保存成csv文件。

<!--[if !vml]--><!--[endif]-->

当导出到csv文件完成后,你就很容易定位到你的方法和函数,并看到分配了多少内存。

<!--[if !vml]--><!--[endif]-->

使用注释简化结果

如果你知道你要探测哪一个方法,你可以只在当该方法被调用时启用探测器,换句话说,你可以从应用程序启动CLR探测器。

为了从C#代码中启动探测器,首选需要引用‘CLRProfilerControl.dll’,可以从Profiler.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探测器并开始我们的应用程序,请确保你没有启用探测的活动复选项,因为我们会在代码中启用探测器。

<!--[if !vml]--><!--[endif]-->

现在如果你查看柱形图,你将看到有限的数据,你看到它只记录了‘System.String’ ‘System.Text.StringBuilder’ 数据类型内存分配消耗情况。

<!--[if !vml]--><!--[endif]-->

如果你查看分配图,你就会看到它现在非常简洁。那个杂乱的视图完全的不见了,现在看到的是一个漂亮简单的视图。在‘Everything’上点击就看到保留的函数。

<!--[if !vml]--><!--[endif]-->

 

如前所说,不要完全相信执行时间

在摘要页你能看到 comments 按钮,如果你点击comments按钮就会显示开始时间和结果时间。不要完全相信这个时间记录,我们前面明确的指出过它是一个侵入式工具,所以结果并不正确。

<!--[if !vml]--><!--[endif]-->


Entering loop ( 1.987  secs)
Exiting loop (
2.022  secs)

 

结论

  • <!--[if !supportLists]-->CLR探测器可以用来查找函数、类和程序集的内存分配情况,并评估性能。
  • <!--[if !supportLists]-->它不能用于产品。
  • <!--[if !supportLists]-->它不能用于性能评估的开始点。我们应该先运行性能计数器,得到方法的高效执行时间,然后再使用CLR探测器查看真实的原因。
  • <!--[if !supportLists]--> <!--[endif]-->可以使用柱形图查看内存分配情况,使用调用图查看方法智能内存分配情况。
  • <!--[if !supportLists]-->如果你知道你要探测哪一个方法,你可以从程序启动探测器。

 

用上面的一切推演出一个作为最佳实践的结论,当你要查看内存分配情况时,没有任何东西能和CLR探测器匹敌。

 

源代码

可以从这里找到示例中的源代码。



本文转自温景良(Jason)博客园博客,原文链接:http://www.cnblogs.com/wenjl520/archive/2010/07/16/1778973.html,如需转载请自行联系原作者
相关文章
|
5月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
53 3
|
5月前
|
SQL 开发框架 .NET
ASP.NET连接SQL数据库:详细步骤与最佳实践指南ali01n.xinmi1009fan.com
随着Web开发技术的不断进步,ASP.NET已成为一种非常流行的Web应用程序开发框架。在ASP.NET项目中,我们经常需要与数据库进行交互,特别是SQL数据库。本文将详细介绍如何在ASP.NET项目中连接SQL数据库,并提供最佳实践指南以确保开发过程的稳定性和效率。一、准备工作在开始之前,请确保您
394 3
|
2月前
|
安全 测试技术 数据库
代码危机:“内存溢出” 事件的深度剖析与反思
初涉编程时,我坚信严谨逻辑能让代码顺畅运行。然而,“内存溢出”这一恶魔却以残酷的方式给我上了一课。在开发电商平台订单系统时,随着订单量增加,系统逐渐出现处理迟缓甚至卡死的情况,最终排查发现是订单状态更新逻辑中的细微错误导致内存无法及时释放,进而引发内存溢出。这次经历让我深刻认识到微小错误可能带来巨大灾难,从此对待代码更加谨慎,并养成了定期审查和测试的习惯。
48 0
|
3月前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
126 6
|
3月前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
104 5
|
4月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
102 1
|
4月前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
4月前
|
Kubernetes Cloud Native Ubuntu
庆祝 .NET 9 正式版发布与 Dapr 从 CNCF 毕业:构建高效云原生应用的最佳实践
2024年11月13日,.NET 9 正式版发布,Dapr 从 CNCF 毕业,标志着云原生技术的成熟。本文介绍如何使用 .NET 9 Aspire、Dapr 1.14.4、Kubernetes 1.31.0/Containerd 1.7.14、Ubuntu Server 24.04 LTS 和 Podman 5.3.0-rc3 构建高效、可靠的云原生应用。涵盖环境准备、应用开发、Dapr 集成、容器化和 Kubernetes 部署等内容。
160 5
|
4月前
|
存储 JavaScript 前端开发
如何优化代码以避免闭包引起的内存泄露
本文介绍了闭包引起内存泄露的原因,并提供了几种优化代码的策略,帮助开发者有效避免内存泄露问题,提升应用性能。

热门文章

最新文章