此文包含以下内容
- 运行时分析
- 性能计数器
- 其他性能分析工具
运行时分析
使用perfmon.exe
跟踪性能,即性能监视器(Performance Monitor)
它是Windows
自带的一款分析.NET
应用程序的工具,它以图形的方式来表示从内存管理到JIT性能的方方面面。通过它,我们也可以知道我们应用程序所使用的资源的情况。
通过 Win + R
的方式,唤出命令提示符工具。在输入框中输入 perfmon.exe
(在Windows10里面,如果在开始菜单出输入,一定要输入全部字符,否则敲回车时可能运行的是系统推荐的应用),然后回车即可,如下:
回车后:
然后,点击左侧栏中【监视工具】下的【性能监视器】, 得到如下图形:点击右侧面板中的“绿色加号”,以添加计数器,如下
可以看到,.NET Framework
应用程序有许多预定义的对象,包括用于内存管理(.NET CLR Memory
)、互操作性(.NET CLR Interop
)、异常处理(.NET CLR Exceptions
)以及多线程处理(.NET CLR LocksAndThreads
)的计数器等多种工具。
点击列表中项目右边的“向下箭头”,可以查看该性能对象能够支持的计数器
单击要查看的性能计数器。
在《选定对象的实例》列表框中,单击<所有实例>,指定要在全局(也就是在整个系统范围内)监视CLR的性能计数器。也可以在《选定对象的实例》列表框中,单击要监视该应用程序的性能计数器的应用程序的名称。
选择了对象实例之后,点击对话框右下方的【确定】按钮,既可以完成添加。
步骤如下图:
以编程方式读取和创建性能计数器
除了以上通过其他工具的方式外,我们也可以在某些情况下(比如只需要测试某一段代码的性能的时候)使用代码的方式来处理,这种情况下,用代码或许更加高效。
.NET 为我们提供了以下类(命名空间System.Diagnostics
)来做这些事情
PerformanceCounter
:表示Windows NT
性能计数器组件。使用此类可读取现有的计数器,或自定义更合适的计数器。也可以向自定义计数器写入性能数据PerformanceCounterCategory
: 提供与计数器交互的一些方法PerformanceCounterInstaller
:用于指定PerformanceCounter
组件的安装程序PerformanceCounterType
:指定PerformanceCounter
计算下一个值(NextValue
)使用的公式
示例代码如下:
using System;
using System.Collections;
using System.Diagnostics;
public class App {
private static PerformanceCounter avgCounter64Sample;
private static PerformanceCounter avgCounter64SampleBase;
public static void Main() {
ArrayList samplesList = new ArrayList();
/// 用于创建目录/类别
/// 如果不存在,则需要创建,此时不能立即创建计数器,需要过一会儿才能使用
if (SetupCategory())
return;
CreateCounters();
CollectSamples(samplesList);
CalculateResults(samplesList);
}
private static bool SetupCategory() {
if (!PerformanceCounterCategory.Exists("AverageCounter64SampleCategory")) {
CounterCreationDataCollection counterDataCollection = new CounterCreationDataCollection();
// 为目录添加计数器
CounterCreationData averageCount64 = new CounterCreationData {
CounterType = PerformanceCounterType.AverageCount64,
CounterName = "AverageCounter64Sample"
};
counterDataCollection.Add(averageCount64);
CounterCreationData averageCount64Base = new CounterCreationData {
CounterType = PerformanceCounterType.AverageBase,
CounterName = "AverageCounter64SampleBase"
};
counterDataCollection.Add(averageCount64Base);
// 创建目录
PerformanceCounterCategory.Create("AverageCounter64SampleCategory",
"Demonstrates usage of the AverageCounter64 performance counter type.",
PerformanceCounterCategoryType.SingleInstance, counterDataCollection);
return true;
} else {
// 目录已经存在,因此我们可以进行后续步骤
return false;
}
}
private static void CreateCounters() {
// 创建计数器
avgCounter64Sample = new PerformanceCounter("AverageCounter64SampleCategory", "AverageCounter64Sample", false);
avgCounter64SampleBase = new PerformanceCounter("AverageCounter64SampleCategory", "AverageCounter64SampleBase", false);
avgCounter64Sample.RawValue = 0;
avgCounter64SampleBase.RawValue = 0;
}
private static void CollectSamples(ArrayList samplesList) {
Random r = new Random(DateTime.Now.Millisecond);
for (int j = 0; j < 100; j++) {
int value = r.Next(1, 10);
Console.Write(j + " = " + value);
avgCounter64Sample.IncrementBy(value);
avgCounter64SampleBase.Increment();
if ((j % 10) == 9) {
OutputSample(avgCounter64Sample.NextSample());
samplesList.Add(avgCounter64Sample.NextSample());
} else
Console.WriteLine();
System.Threading.Thread.Sleep(50);
}
}
private static void CalculateResults(ArrayList samplesList) {
for (int i = 0; i < (samplesList.Count - 1); i++) {
// 输出样本信息
OutputSample((CounterSample)samplesList[i]);
OutputSample((CounterSample)samplesList[i + 1]);
// 通过.NET自带方法,计算计数器的值
float counterValue = CounterSampleCalculator.ComputeCounterValue((CounterSample)samplesList[i], (CounterSample)samplesList[i + 1]);
Console.WriteLine($".NET computed counter value = {counterValue}");
}
}
private static void OutputSample(CounterSample s) {
Console.WriteLine("\r\nSample values - \r\n");
Console.WriteLine($" BaseValue = {s.BaseValue}");
Console.WriteLine($" CounterFrequency = {s.CounterFrequency}");
Console.WriteLine($" CounterTimeStamp = {s.CounterTimeStamp}");
Console.WriteLine($" CounterType = {s.CounterType}");
Console.WriteLine($" RawValue = {s.RawValue}");
Console.WriteLine($" SystemFrequency = {s.SystemFrequency}");
Console.WriteLine($" TimeStamp = {s.TimeStamp}");
Console.WriteLine($" TimeStamp100nSec = {s.TimeStamp100nSec}\r\n");
}
}
复制代码
性能计数器
这个小节将对这个计数器列表中很常用的计数器进行简要说明
包括以下计数器:
- 异常性能计数器
- 互操作性能计数器
- JIT 性能计数器
- 加载性能计数器
- 锁定和线程性能计数器
- 内存性能计数器
- 联网性能计数器
- 安全性能计数器
异常性能计数器
.NET CLR Exceptions
类别包含的计数器提供应用程序引发的异常的相关信息
- 引发的异常数: 从应用程序启动以来引发的异常总数。此数值包括
.NET
异常和转换为.NET
异常的非托管异常(例如,从非托管代码返回的HRESULT
在托管代码中会被转换为异常)。 - 引发的异常数/秒: 显示每秒引发的异常数,它包括已处理和未经处理的异常。此计数器是一个潜在性能问题(如果引发较多数目
>100
的异常)的指示器。 - 筛选次数/秒:显示每秒执行的
.NET
异常筛选次数。 Finally
数量/秒:显示每秒执行的finally
块的数量。特别注意的是,此计数器只计算有异常执行的finally
块(即抛出异常之后),不计算正常代码路径上的finally
块。- 捕获的深度/秒:显示从引发异常的帧到处理该异常的帧每秒遍历的堆栈帧数。
互操作性能计数器
.NET CLR Interop
(互操作)类别包括的计数器提供应用程序与COM
组件、COM+
服务和外部类型库交互的相关信息
CCW
数目:它显示非托管COM
代码所引用的托管对象数。CCW
是指正在从非托管COM
客户端引用的托管对象的代理- 封送处理次数:显示从应用程序启动以来,将参数和返回值从托管代码封送至非托管代码(反之亦然)的总次数
- 存根数:显示由公共语言运行时创建的当前存根数。存根负责在
COM
互操作调用或P-Invoke
调用期间将参数和返回值从托管代码封送至非托管代码(或反之)
JIT 性能计数器
.NET CLR JIT
类别包括的计数器提供的由JIT
编译相关信息
JIT
编译的IL
字节数:从应用程序启动以来,由实时 (JIT
) 编译器编译的微软中间语言 (MSIL
) 字节总数JIT
编译的方法数:从应用程序启动以来JI
T编译的方法总数。此计数器不包括预先进行
JIT` 编译的方法JIT
所占时间百分比:显示自上次JIT
编译阶段以来JIT
编译所用运行占用时间的百分比JIT
每秒编译的IL
字节数:显示每秒JIT
编译的MSIL
字节数JIT
失败数:自应用程序启动以来JIT
编译器编译失败的方法的高峰数量
加载性能计数器
.NET CLR Loading
(加载)类别包括的计数器提供已加载的程序集、类和应用程序域的相关信息
- 加载程序堆中的字节数:所有应用程序域中类加载程序当前提交内存大小(以字节为单位)
- 当前
AppDomain
数:应用程序中加载的应用程序域数量 - 当前程序集数:当前运行的应用程序中所有应用程序域范围内已加载的程序集数量。如果程序集以非特定于域的形式从多个应用程序域中加载,则此计数器只递增一次
- 当前已加载的类:所有程序集中已加载类的数量
AppDomain
速率:每秒加载的应用程序域的数量- 卸载
AppDomain
的速率:每秒卸载的应用程序域的数量 - 程序集速率:所有应用程序域范围内每秒加载的程序集数量。如果程序集以非特定于域的形式从多个应用程序域中加载,则此计数器只递增一次
- 加载类的速率:所有程序集中每秒加载的类的数量
- 加载失败的速率:每秒加载失败的类的数量。加载失败的原因有很多,例如安全性不够或格式无效等等
- 加载失败总数:自应用程序启动以来加载失败的类的峰值
AppDomain
总数:自应用程序启动以来已加载的应用程序域的峰值。- 卸载的
AppDomain
总数:显示自应用程序启动以来卸载的应用程序的峰值。如果应用程序域加载和卸载多次,则此计数器将在每次卸载应用程序域时递增。 - 程序集总数:自应用程序启动以来加载的程序集总数
- 已加载的类总数:自应用程序启动以来所有程序集中加载的类的累计数量。
锁定和线程性能计数器
.NET CLR LocksAndThreads
(锁和线程)类别包括的计数器提供应用程序所使用的托管锁和托管线程的相关信息
- 当前逻辑线程数目:应用程序中当前托管的线程数。此计数器包括将正在运行和已停止的线程
- 当前物理线程数目:
CLR
创建和拥有的,用作托管线程的基础线程,的本机操作系统线程数。此计数器的值不包括CLR
在其内部操作中使用的线程;它是操作系统进程中线程的子集。 - 当前已识别的线程数目:当前已由
CLR
识别的线程数。这些线程与对应的托管线程对象相关联。CLR
不创建这些线程,但它们在CLR内
至少会运行一次。具有相同线程 ID 的线程不会进行两次计数 - 已识别的线程总数:自应用程序启动以来已由
CLR
识别的线程总数 - 竞争率/秒:运行时中线程尝试获取托管锁的失败率。
- 当前队列长度:当前正在等待获取应用程序托管锁的线程总数
- 队列长度/秒:每秒内等待获取应用程序锁的线程数目
- 队列长度峰值:自应用程序启动以来等待获取托管锁的线程总数峰值。
- 已识别线程的速率/秒:每秒内由
CLR
识别的线程数 - 争用总数:运行中线程尝试获取托管锁失败的总次数
内存性能计数器
.NET CLR Memory
(内存)类别包括的计数器提供GC
的相关信息
- 所有堆中的字节数:第
1
代堆、第2
代堆和大型对象堆的总和,即垃圾回收堆上分配的当前内存(以字节为单位) GC
句柄数:正在使用的垃圾回收句柄的数量。垃圾回收句柄:CLR
和托管环境外部的资源的句柄- 第
0
代回收次数:自应用程序启动以来第0
代对象(即最年轻、最近分配的对象)进行垃圾回收的次数 - 第
1
代回收次数:自应用程序启动以来第1
代对象进行垃圾回收的次数 - 第
2
代回收次数:自应用程序启动以来第2
代对象进行垃圾回收的次数 - 已引发
GC
数:因显式调用GC.Collect
而执行的垃圾回收次数峰值 - 固定对象数目:在上一次垃圾回收中遇到的固定对象(垃圾回收器不能移入内存的对象)的数目
- 正在使用的同步块数目:正在使用的同步块的数量。 同步块是用于存储同步信息而为每个对象分配的数据结构,但它不限于只存储同步信息,也可以存储
COM
互操作元数据 - 已提交的字节总数:
GC
当前已提交的虚拟内存总量(以字节为单位) - 已保留的字节总数:
GC
当前保留的虚拟内存量(以字节为单位) GC
所占时间百分比:执行上次垃圾回收与执行垃圾回收所用时间的百分比。此计数器通常指示GC
代表应用程序收集和压缩内存所执行的作业- 分配的字节数/秒:堆上每秒分配的字节数。注意,此计数器在每次垃圾回收结束时(而非每次分配时)更新
- 最终存活对象:在终结(
Finalize
)任务后,仍然存在垃圾回收对象数。 如果这些对象具有对其他对象的引用,则那些对象也会存在,但是不计入此计数器内 - 第
0
代堆大小:第0
代中可以分配的最大字节数;但它不能确定第0
代中已分配的字节数 - 第
0
代提升的字节数/秒:每秒从第0
代提升到第1
代的字节数 - 第
1
代堆大小:第1
代中的当前字节数(切记,此代中的对象不是直接分配的;这些对象是从以前的第0
代垃圾回收提升的) - 第
1
代提升的字节数/秒:每秒从第1
代提升到第2
代的字节数 - 第
2
代堆大小:第2
代中的当前字节数,不包括大对象,切记:此代中的对象(不包括大对象)不是直接分配的 - 大型对象堆大小:大型对象堆的当前大小。垃圾回收器将大于
85000
字节左右的对象视作大对象并且直接在大对象堆中分配;它们不按照级别来提升 - 进程 ID:显示被监视的
CLR
进程实例的进程 ID。 - 从第
0
代提升的终止内存:由于等待终结而从第0
代提升到第1
代的内存字节数 - 从第
0
代提升的内存:垃圾回收后仍存在并从第0
代提升到第1
代的内存字节数 - 从第
1
代提升的内存:显示垃圾回收后仍存在并从第1
代提升到第2
代的内存字节数
联网性能计数器
.NET CLR Networking
(网络)类别包括的计数器提供应用程序通过网络发送和接收的数据的相关信息
- 已接收的字节数:自进程启动以来,
AppDomain
中的所有Socket
对象接收到的字节的总数。此数据包括未定义的任何协议信息的TCP/IP
数据 - 已发送的字节数:自进程启动以来,
AppDomain
中的所有Socket
对象已发送的字节的累积总数。此数据包括未定义的任何协议信息的TCP/IP数据 - 已建立的连接:自进程启动以来,
AppDomain
中已经连接的Socket
对象的累积总数 - 已接收的数据报:自进程启动以来,
AppDomain
中的所有Socket
对象接收到的数据报包的总数 - 已发送的数据报:自进程启动以来,
AppDomain
中的所有Socket
对象已发送的数据报包的总数 HttpWebRequest
平均生存期:自进程启动以来,AppDomain
中在上一个间隔中结束的所有HttpWebRequest
对象的平均时间HttpWebRequest
平均排队时间:自进程启动以来,AppDomain
中在上一个间隔中结束的所有HttpWebRequest
对象的平均排队时间- 创建的
HttpWebRequest
/秒:AppDomain
中每秒创建的HttpWebRequest
对象的数目 - 已排队的
HttpWebRequest
/秒:AppDomain
中每秒添加到队列的HttpWebRequest
对象的数量 - 已中止的
HttpWebRequest
/秒:AppDomain
中应用程序每秒调用Abort
方法的HttpWebRequest
对象的数量。 - 失败的
HttpWebRequest
/秒:AppDomain
中每秒从服务器接收失败状态码的HttpWebRequest
对象的数量。
其他的网络性能计数器,如:
- 事件计数器:用于测量某些事件的发生次数
- 数据计数器:用于测量已发送或已接收的数据量
- 持续时间计数器:测量不同进程花费的时间。测量对象每个间隔(通常以秒计)退出不同状态后的次数
- 每间隔计数器:用于测量每个间隔(通常以秒计)中正在进行特定转换的对象数
网络性能计数器包含在两个类别中:
.NET CLR
网络:.NET Framework 2
上引入且在.NET Framework 2
及更高版本上受支持的原始性能计数器。.NET CLR
网络 4:所有上述套接计数器和.NET Framework 4
及更高版本上受支持的新的性能计数器
安全性能计数器
.NET CLR Security
(安全性)类别包括的计数器提供公共语言运行时针对应用程序执行的安全检查的相关信息
- 链接时检查次数:自应用程序启动以来链接时代码(link-time code)访问安全检查的总次数。 当调用方要求实时 (
JIT
) 编译时的特定权限时,执行链接时代码访问安全检查 RT
检查所占的时间百分比:自上一次取样以来执行运行时代码访问安全检查所用运行时间的百分比- 堆栈审核深度:在上次运行时代码访问安全检查期间的堆栈深度
- 运行时检查总数:自应用程序启动以来执行的运行时代码访问安全检查的总数。当调用方要求特定权限时,执行运行时代码访问安全检查。 运行时检查在调用方每次调用时都会执行,并会检查调用方的当前线程堆栈。 此计数器与“堆栈审阅深度”计数器一起使用时可指示安全检查出现的性能损失。
其他性能分析工具
- JetBrains dotTrace:它可以帮助你优化应用程序性能指标,支持
.NET1.0
版本到4.5
,快速分析程序瓶颈,找出影响效率的代码。官方网站上有10
天试用版 - ANTS Performance Profiler:
ANTS
性能分析器是一种用于分析.NET
框架支持的用任何语言编写的应用程序的工具。ANTS
性能分析器能分析所有.NET
应用程序,包括ASP.NET
网络应用程序、Windows
服务和COM+
应用程序。ANTS
性能分析器能在几分钟内识别性能瓶颈,运行非常快速,且响应时,对程序的执行具有最低影响。 - NET Memory Profiler:一款非常深入分析
.NET
内存的优化工具,快速发现内存泄漏问题,并且自动进行内存检测 VS
自带的性能分析工具:如图 点击之后,如下图 可以看到,由CPU
、GPU
、内存及性能向导四个选项,选择您想要进行的性能分析,点击【Start/开始】就可以了
至此,本节内容讲解完毕。欢迎关注公众号【嘿嘿的学习日记】,所有的文章,都会在公众号首发,Thank you~