有数据,有真相,相信大家在平时的工作或学习过程中,都需要比较几种不同方法或实现之间的性能差距。在这些时候,往往就需要我们不断地创建Stopwatch,打开,关闭,然后打印时间。这种一遍又一遍的重复终有一天会让人忍无可忍,因此如果能有一个“标准”的性能计数器,那应该可以让生活轻松许多。这个性能计数器不用复杂,够用就好;也不需要考虑扩展性,要扩展时直接修改代码就够了;同样不需要考虑输出格式,直接打印在Console就行。

在上次的.NET技术大会中,Jeffrey Richter大叔在Keynote Session中进行了一个名为“The Performance of Everyday Things”的主题演讲,展示了各种常用编程元素之间的性能对比。在演示中他使用了一个名为CodeTimer的简单计数器,用于统计每种做法的性能。可惜翻遍了每个地方都没发现JR大叔在哪里公开了这个计数器的实现。算了,那么就凭着印象写一个出来吧,反正也不复杂。

总的来说,CodeTimer有两个公开方法,一个是Initialize,一个是Time:

public static class CodeTimer
{public static void Initialize(){Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;Thread.CurrentThread.Priority = ThreadPriority.Highest;Time("", 1, () => { });}public static void Time(string name, int iteration, Action action){...}
}

CodeTimer.Initialize方法应该在测试开始前调用。首先它会把当前进程及当前线程的优先级设为最高,这样便可以相对减少操作系统在调度上造成的干扰。然后调用一次Time方法进行“预热”,让JIT将IL编译成本地代码,让Time方法尽快“进入状态”。Time方法则是真正用于性能计数的方法,实现如下:

public static void Time(string name, int iteration, Action action)
{if (String.IsNullOrEmpty(name)) return;// 1.ConsoleColor currentForeColor = Console.ForegroundColor;Console.ForegroundColor = ConsoleColor.Yellow;Console.WriteLine(name);// 2.GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);int[] gcCounts = new int[GC.MaxGeneration + 1];for (int i = 0; i <= GC.MaxGeneration; i++){gcCounts[i] = GC.CollectionCount(i);}// 3.Stopwatch watch = new Stopwatch();watch.Start();ulong cycleCount = GetCycleCount();for (int i = 0; i < iteration; i++) action();ulong cpuCycles = GetCycleCount() - cycleCount;watch.Stop();// 4.Console.ForegroundColor = currentForeColor;Console.WriteLine("\tTime Elapsed:\t" + watch.ElapsedMilliseconds.ToString("N0") + "ms");Console.WriteLine("\tCPU Cycles:\t" + cpuCycles.ToString("N0"));// 5.for (int i = 0; i <= GC.MaxGeneration; i++){int count = GC.CollectionCount(i) - gcCounts[i];Console.WriteLine("\tGen " + i + ": \t\t" + count);}Console.WriteLine();
}private static ulong GetCycleCount()
{ulong cycleCount = 0;QueryThreadCycleTime(GetCurrentThread(), ref cycleCount);return cycleCount;
}[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool QueryThreadCycleTime(IntPtr threadHandle, ref ulong cycleTime);[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentThread();

Time方法接受三个参数,名称,循环次数以及需要执行的方法体。打印出花费时间,消耗的CPU时钟周期,以及各代垃圾收集的回收次数。具体实现分几个步骤,如下:

  1. 保留当前控制台前景色,并使用黄色输出名称参数。
  2. 强制GC进行收集,并记录目前各代已经收集的次数。
  3. 执行代码,记录下消耗的时间及CPU时钟周期1
  4. 恢复控制台默认前景色,并打印出消耗时间及CPU时钟周期。
  5. 打印执行过程中各代垃圾收集回收次数。

与传统计数方法相比,这段代码还输出了更多信息:CPU时钟周期及各代垃圾收集回收次数。CPU时钟周期是性能计数中的辅助参考,说明CPU分配了多少时间片给这段方法来执行,它和消耗时间并没有必然联系。例如Thread.Sleep方法会让CPU暂时停止对当前线程的“供给”,这样虽然消耗了时间,但是节省了CPU时钟周期:

CodeTimer.Time("Thread Sleep", 1, () => { Thread.Sleep(3000); });
CodeTimer.Time("Empty Method", 10000000, () => { });

结果如下:

而垃圾收集次数的统计,即直观地反应了方法资源分配(消耗)的规模:

int iteration = 100 * 1000;string s = "";
CodeTimer.Time("String Concat", iteration, () => { s += "a"; });StringBuilder sb = new StringBuilder();
CodeTimer.Time("StringBuilder", iteration, () => { sb.Append("a"); });

结果如下:

老赵最近在研究一个问题的几种不同做法在性能上的优劣,其中CodeTimer起到了很重要的作用——这边也先卖个关子,接下来老赵也将会写几篇文章来讲解这个问题。

注1:统计CPU时钟周期时使用P/Invoke访问QueryThreadCycleTime函数,这是Vista和Server 2008中新的函数。感谢装配脑袋在这里提供的帮助。

注2:对于.NET 2.0及Vista以下操作系统,请参考《对老赵写的简单性能计数器的修改》

一个简单的性能计数器:CodeTimer相关推荐

  1. 程序猿修仙之路--数据结构之你是否真的懂数组? c#socket TCP同步网络通信 用lambda表达式树替代反射 ASP.NET MVC如何做一个简单的非法登录拦截...

    程序猿修仙之路--数据结构之你是否真的懂数组? 数据结构 但凡IT江湖侠士,算法与数据结构为必修之课.早有前辈已经明确指出:程序=算法+数据结构  .要想在之后的江湖历练中通关,数据结构必不可少.数据 ...

  2. 设计一个简单的四则计算器

    c# using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; u ...

  3. 聊聊高并发(十六)实现一个简单的可重入锁

    可重入锁指的是假设一个线程已经获得了一个锁,那么它能够多次进入这个锁,当然前提是线程须要先获得这个锁. 可重入锁是最常使用的锁.Java的内置锁就是可重入锁,使用synchronizedkeyword ...

  4. 用php做一个简单的汇率,vue实现简单实时汇率计算功能

    最近在自己摸索vue的使用,因为相对于只是去看教程和实例,感觉不如自己动手写一个demo入门来的快.刚好看到小程序中有一个简单但是很精致的应用极简汇率,而且它的表现形式和vue的表现形式很像,于是想着 ...

  5. python编写赛车游戏单机版_使用Python中OrderedDict模拟一个简单的竞速游戏排名

    上一篇,我们梳理了Python中关于字典排序的一些常用方法(杂乱无章的数据结构如何进行排序,简明讲述Python字典排序那些事).其中,我们讲到了Python的collections模块中的Order ...

  6. 编写一个最简单的.php,学习猿地- 说明 如果我们要编写一个简单的PHP脚本,需要学习哪些...

    说明 如果我们要编写一个简单的 PHP 脚本,需要学习哪些基础知识呢? PHP 基础 PHP 脚本可放置于文档中的任何位置. 标准 的 PHP 脚本以 <?php 开头,以 ?> 结尾: ...

  7. 【javamatlab】以一个简单的例子实现java和matlab混编

    目录 使用环境: MATLAB: matlab代码: 将matlab代码打包: eclipse: jar包配置: 使用jar包: 使用环境: jdk8(ide使用eclipse2019-6).matl ...

  8. 一个简单的slider滑块组件

    2019独角兽企业重金招聘Python工程师标准>>> 我们先来看一张图片: 要实现这样的效果我们有很多种方法,比如直接使用<input type="range&qu ...

  9. 用 cooking 搭建一个简单又优雅的 Vue 项目开发环境 (入门篇)

    本文适合 Vue 的初学者,以及对 webpack 不熟悉的同学阅读.前提是你要会用基本的命令行. Node 和 NPM,以及掌握 ES2015 的基础知识.本文都是在 macOS 环境下运行,要求使 ...

最新文章

  1. Python 函数不定长参数
  2. exit()与_exit()的区别(转)
  3. mysql innodb myisam 混合,MySQL MyIsam/InnoDB混合在一起的事务
  4. Flex与Javascript交互
  5. Golang的调度模型
  6. pip 20.3 发布:更改默认依赖解析器、即将停止支持 Python 2.7
  7. sql分区表上创建索引_SQL Server中分区表和索引的选项
  8. windows主机的linux虚拟机中使用neovim复制、粘贴
  9. ReentrantReadWriteLock 可重入的读写锁
  10. pythonclass使用教程_【Python 1-15】Python手把手教程之——详解类Class以及类的使用...
  11. 编程基本功:正常运行的代码,你看明白能做什么?不如解决几个简单BUG
  12. (转载addone)完全使用Linux作为桌面系统 —— 使用Linux两年记 --软件列表
  13. 【系统分析师之路】系统分析师冲刺习题集(数学与经济管理)
  14. 肌电信号分析相关链接分享
  15. 极化码理论及算法研究后续(代码讲解)
  16. word引用 html文件路径,Word怎么引用网页文档
  17. MacOS程序和库签名的问题
  18. 嵌入式软件开发面试——一个应届生求职的亲身经历
  19. 英语六级口语 计算机,英语六级口语
  20. .net MVC全局定时器执行作业

热门文章

  1. 信息学奥赛一本通(1281:最长上升子序列)
  2. 连连看(HDU-1175)
  3. 43 FI配置-财务会计-固定资产-一般评估-定义折旧范围
  4. html视频鼠标移除不播放,html - 在Mouseover上播放Gif并在鼠标移除时暂停Gif而不替换图像? - 堆栈内存溢出...
  5. python编程基础张勇答案_Python程序开发、编程基础阶段试题及答案
  6. Docker创建一个镜像
  7. 201312-2_ISBN号码
  8. [有限元] Ansys Workbench 在对称问题中使用 Symmetry Region
  9. 钉钉开放平台:内网穿透工具 - 服务器免费打造教程
  10. WordPress一个还不错的404html单页代码