温故之.NET性能分析

此文包含以下内容

  • 运行时分析
  • 性能计数器
  • 其他性能分析工具

运行时分析

使用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编译的方法数:从应用程序启动以来JIT 编译的方法总数。此计数器不包括预先进行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 检查所占的时间百分比:自上一次取样以来执行运行时代码访问安全检查所用运行时间的百分比
  • 堆栈审核深度:在上次运行时代码访问安全检查期间的堆栈深度
  • 运行时检查总数:自应用程序启动以来执行的运行时代码访问安全检查的总数。当调用方要求特定权限时,执行运行时代码访问安全检查。 运行时检查在调用方每次调用时都会执行,并会检查调用方的当前线程堆栈。 此计数器与“堆栈审阅深度”计数器一起使用时可指示安全检查出现的性能损失。

其他性能分析工具

  1. JetBrains dotTrace:它可以帮助你优化应用程序性能指标,支持.NET1.0版本到4.5,快速分析程序瓶颈,找出影响效率的代码。官方网站上有10天试用版
  2. ANTS Performance ProfilerANTS性能分析器是一种用于分析.NET框架支持的用任何语言编写的应用程序的工具。ANTS性能分析器能分析所有.NET应用程序,包括ASP.NET网络应用程序、Windows服务和COM+应用程序。ANTS性能分析器能在几分钟内识别性能瓶颈,运行非常快速,且响应时,对程序的执行具有最低影响。
  3. NET Memory Profiler:一款非常深入分析.NET内存的优化工具,快速发现内存泄漏问题,并且自动进行内存检测
  4. VS自带的性能分析工具:如图
    点击之后,如下图
    可以看到,由CPUGPU、内存及性能向导四个选项,选择您想要进行的性能分析,点击【Start/开始】就可以了

至此,本节内容讲解完毕。欢迎关注公众号【嘿嘿的学习日记】,所有的文章,都会在公众号首发,Thank you~

转载于:https://juejin.im/post/5b2a25546fb9a00e894587e6

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值