当前位置: 首页 > news >正文

.NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)

我们有很多种方法评估一个方法的执行耗时,比如使用性能分析工具,使用基准性能测试。不过传统的在代码中编写计时的方式依然有效,因为它可以生产环境或用户端得到真实环境下的执行耗时。

如果你希望在 .NET/C# 代码中编写计时,那么阅读本文可以获得一些建议。阅读本文也可以了解到 QueryPerformanceCounterGet­System­Time­As­File­Time 等方法的差异。


本文内容

      • 基本的计时
      • 结论:使用什么方法计时
      • 各种计时 API 及其比较
        • 基于系统性能计数器(QPC)的 API
        • 基于系统时间的 API
        • 基于 QPC 和系统时间的 API
        • 参考资料

基本的计时

计时一般采用下面这种方式,在方法执行之前获取一次时间,在方法结束之后再取得一次时间。

// 在方法开始之前。
Foo();
// 在方法执行之后。

这样,前后两次获取的时间差即为方法 Foo 的执行耗时。

这里我不会提到性能测试工具或者基准性能测试这些方法,因为这些测试代码不会运行于用户端。你可以阅读以下博客获得这两者的使用:

  • C# 标准性能测试 - 林德熙
  • C# 标准性能测试高级用法 - 林德熙
  • .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法) - 吕毅

结论:使用什么方法计时

先说结论:System.Diagnostics 命名空间下有一个 Stopwatch 类。如果你要为你方法的执行时间进行统计,那么就使用这个类。

Stopwatch 类有一些静态属性、也有一些实例方法和实例属性。此类型的时间统计是按照高性能和高精度的要求来做的,于是你可以用它获得高精度的计时效果。不过,如果你对性能要求近乎苛刻,例如你的方法会被数百万次或更高频地执行,那么就需要开始斟酌如何调用里面的属性了。

简单的使用如下面这样:

var watch = Stopwatch.StartNew();
Foo();
watch.Stop();
var elapsed = watch.Elapsed;

当然,你也可以直接使用 Stopwatch 的构造函数,new 出来之后再 Start,不过 StartNew 静态方法可以将两句合并为一句。

各种计时 API 及其比较

计时还有很多的方法,你可以针对不同需求场景使用不同的方法。不过,如果你根本没有了解过其他方法的话,那么建议直接使用上面的 Stopwatch,不要想太多。

现在,我们看看 Windows 下的计时还有哪些 API:

  • 基于 QPC 的高精度 API
    • Query­Performance­Counter
    • Query­Performance­Frequency
  • 基于系统时间的非高精度 API
    • Get­Tick­Count, Get­Tick­Count64
    • Get­Message­Time
    • Get­System­Time, Get­Local­Time, Get­System­Time­As­File­Time
    • Query­Interrupt­Time, Query­Unbiased­Interrupt­Time
  • 基于 QPC 和系统时间的 API
    • Get­System­Time­Precise­As­File­Time
    • Query­Interrupt­Time­Precise, Query­Unbiased­Interrupt­Time­Precise

基于系统性能计数器(QPC)的 API

QueryPerformanceCounter,微软文档中把它称之为 QPC。

一般情况下使用的 QueryPerformanceCounter,内核驱动开发者使用的 KeQueryPerformanceCounter 和 .NET 开发者使用的 System.Diagnostics.Stopwatch 都是基于 QPC 的 API。

QPC 是通过计算机上独立运行的高精度硬件计时模块来获得时间戳的。这意味着,使用此 API 获得的时间戳是本机时间戳,不包含任何时区等信息。

由于 QPC 的高精度特性,所以非常适合在单个设备上测量一个小段时间的时间间隔。而这也符合我们本文一开始说到的方法执行耗时测量需求。

QueryPerformanceCounter 得到的值是 Ticks,单位是 100 ns。

1 tick  = 100 ns
1 us    = 1000 ns
1 ms    = 1000 us
1 s     = 1000 ms

基于系统时间的 API

如果你的需求不止是测量获取一个时间间隔,而是需要一个长期保存的时间,或者需要将时间与其他设备进行通信,那么基于单台设备的 QPC 就不符合要求了。

GetSystemTimeAsFileTime 可以用来获取系统时钟时间。这个时间就是基于系统时钟的,所以如果你的时间戳是用来通信的,那么就很有用。当然,如果要在设备之间进行与时间信息相关的同步,还可能需要使用 NTP(Network Time Protocol)先同步时间。

DateTime.Now 获取时间的方法就是这个:

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern long GetSystemTimeAsFileTime();

这里有一些比较有趣的说法,基于系统时间的 API 也会说成是获取高精度时间,那么跟 QPC 有什么不同呢?

这里我只能拿英文来说话了。来自微软的 Raymond Chen 在它的 The Old New Thing 一书中说,基于系统时间的 API 获取的时间戳精度用的是 “所谓的 Precise”,但实际上应该称之为 “Accurate”,而 QPC 才能称之为实质上的 “Precise”。纠结起来就是 QPC 比基于系统时间的 API 得到的时间戳精度更高。

基于 QPC 和系统时间的 API

Get­System­Time­Precise­As­File­Time 这些 API 既可以获得 QPC 的高精度,又与系统时钟相关,于是你可以使用这些 API 同时获得以上测量的好处。当然,这以性能成本为代价的。


参考资料

  • Acquiring high-resolution time stamps - Microsoft Docs
  • How accurate are the various Windows time-querying functions? – The Old New Thing
  • windows平台时间函数性能比较QueryPerformanceCounter,GetTickCount,ftime,time,GetLocalTime,GetSystemTimeAsFileTime - 小 楼 一 夜 听 春 雨 - 博客园
  • c# - Is DateTime.Now the best way to measure a function’s performance? - Stack Overflow
  • c# - How do I measure how long a function is running? - Stack Overflow
  • c# - Calculate the execution time of a method - Stack Overflow
  • Stopwatch.IsHighResolution Field (System.Diagnostics) - Microsoft Docs
  • Stopwatch.cs
  • timespan.cs

相关文章:

  • 编写 Target 检测 MSBuild / dotnet build 此次编译是否是差量编译
  • 使用 Win2D 绘制带图片纹理的圆(或椭圆)
  • Win2D 中的游戏循环:CanvasAnimatedControl
  • 使用 Windows 10 中的加速度计(Accelerometer,重力传感器)
  • 用 dotTrace 进行性能分析时,各种不同性能分析选项的含义和用途
  • 如何创建一个基于 .NET Core 3 的 WPF 项目
  • 将基于 .NET Framework 的 WPF 项目迁移到基于 .NET Core 3
  • 了解 .NET 的默认 TaskScheduler 和线程池(ThreadPool)设置,避免让 Task.Run 的性能急剧降低
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
  • 在有 UI 线程参与的同步锁(如 AutoResetEvent)内部使用 await 可能导致死锁
  • 不要使用 Dispatcher.Invoke,因为它可能在你的延迟初始化 LazyT 中导致死锁
  • 定义一组抽象的 Awaiter 的实现接口,你下次写自己的 await 可等待对象时将更加方便
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
  • .NET 中什么样的类是可使用 await 异步等待的?
  • Visual Studio 2017 以前的旧格式的 csproj Import 进来的 targets 文件有时不能正确计算属性(PropertyGroup)和集合(ItemGroup)
  • 【EOS】Cleos基础
  • Druid 在有赞的实践
  • ES6语法详解(一)
  • ESLint简单操作
  • gitlab-ci配置详解(一)
  • Javascript弹出层-初探
  • Java教程_软件开发基础
  • js 实现textarea输入字数提示
  • Just for fun——迅速写完快速排序
  • Logstash 参考指南(目录)
  • Python_OOP
  • React的组件模式
  • SpringCloud集成分布式事务LCN (一)
  • Vue.js 移动端适配之 vw 解决方案
  • 分享几个不错的工具
  • 模仿 Go Sort 排序接口实现的自定义排序
  • 详解NodeJs流之一
  • 用Visual Studio开发以太坊智能合约
  • 在GitHub多个账号上使用不同的SSH的配置方法
  • 你对linux中grep命令知道多少?
  • 【运维趟坑回忆录 开篇】初入初创, 一脸懵
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • !!Dom4j 学习笔记
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • (C语言)字符分类函数
  • (libusb) usb口自动刷新
  • (分布式缓存)Redis持久化
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (蓝桥杯每日一题)love
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • .bat批处理出现中文乱码的情况
  • .NET学习全景图
  • .NET运行机制
  • @JsonFormat与@DateTimeFormat注解的使用
  • @RequestBody的使用
  • [ vulhub漏洞复现篇 ] GhostScript 沙箱绕过(任意命令执行)漏洞CVE-2019-6116
  • [1204 寻找子串位置] 解题报告
  • [2018][note]用于超快偏振开关和动态光束分裂的all-optical有源THz超表——
  • [23] GaussianAvatars: Photorealistic Head Avatars with Rigged 3D Gaussians