热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

c#扩展方法奇思妙用性能篇一:扩展方法性能初测

最近写了几篇《c#扩展方法奇思妙用》的文章,一直只是讨论如何扩展、如何使用的问题,几乎没有涉及效率方面。而大家的回复好多都在问效率如何、性能怎样,也引起了我对效率的关注,今天将初步测试的结
最近写了几篇《 c#扩展方法奇思妙用》的文章,一直只是讨论如何扩展、如何使用的问题,几乎没有涉及效率方面。
而大家的回复好多都在问效率如何、性能怎样,也引起了我对效率的关注,今天将初步测试的结果发出来,大家一起探讨一下。

以前没太写过性能测试的代码,上网找了一下,说可以用Stopwatch进行计时,比较准确。
Stopwatch使用比较简单,几个方法从名字上就知道用用途:Reset(计时清零)、Start(开始计时)、Stop(停止计时),属性ElapsedMilliseconds就是执行操作所用的毫秒数。

为了简化测试,让更多人看明白,我们这是对IsNullOrEmpty扩展进行测试,它只是简单调用string.IsNullOrEmpty静态方法。
但为了让我们的测试更有趣一些,我们再加上两个相同功能的方法,一个是IsNullOrEmpty的手工实现版,称为手工方法,另外一个用lambda表达式写的。
一共是如下三个方法与string.IsNullOrEmpty(称为“原方法”)比较:
 1           // 扩展方法
 2           public   static   bool  IsNullOrEmpty1( this   string  s)
 3          {
 4               return   string .IsNullOrEmpty(s);
 5          }
 6           // 手工方法
 7           public   static   bool  IsNullOrEmpty2( string  s)
 8          {
 9               return  s  ==   null   ||  s  ==   string .Empty;
10          }
11           // lambda方法
12           public   static  Func < string bool >  IsNullOrEmpty3  =  s  =>   string .IsNullOrEmpty(s);
我们在函数名后面添加上一个数字,将它们区分开,以避免相互混淆。

为了测试公正,尽量消除测试中的误差,我们采用一个数组存放要测试的字符串。
这个数组中存放三种字符串,非Empty非Null、Empty、Null。随机存入,数量大致相同。生成算法如下:
 1           private   static   string [] GetTestStringArray( int  count)
 2          {
 3               string [] result  =   new   string [count];
 4              Random random  =   new  Random();
 5 
 6               int  r  =   0 ;
 7               for  ( int  i  =   0 ; i  <  count; i ++ )
 8              {
 9                  r  =  random.Next( 3 );
10                   if  (r  ==   0 ) result[i]  =  i.ToString();
11                   else   if  (r  ==   1 ) result[i]  =   string .Empty;
12                   else  result[i]  =   null ;
13              }
14               return  result;
15          }

我们让这四个算法(前面三个算法+原来的静态算法)依次对数组中的每一项进行判断。
有一点要特别注意, 对集合遍历也要耗时,我们要排除这段时间。
下面给出测试算法,写的不好,别见笑:
 1 public   static   void  Test()
 2 {
 3    int count = 10000000;                                 //7个零
 4    string[] ss = GetTestStringArray(count);  //测试字符串Array
 5    bool b;
 6    string str;
 7
 8    long t = 0;    //基本循环时间
 9    long t0 = 0;    //原方法时间
10    long t1 = 0;    //扩展方法时间
11    long t2 = 0;    //手工方法时间
12    long t3 = 0;    //lambda时间
13
14    Stopwatch watch = new Stopwatch();
15    for (int i = 0; i < 10; i++)    //循环测试10次
16    {
17        watch.Reset(); watch.Start();
18        foreach (string s in ss) str = s;
19        watch.Stop();
20        Console.Write("基本循环:" + watch.ElapsedMilliseconds + "ms\t\t\t\t");
21        t += watch.ElapsedMilliseconds;
22
23        watch.Reset(); watch.Start();
24        foreach (string s in ss) { str = s; b = string.IsNullOrEmpty(str); }
25        watch.Stop();
26        Console.Write("原方法:" + watch.ElapsedMilliseconds + "ms\t\t");
27        t0 += watch.ElapsedMilliseconds;
28
29        watch.Reset(); watch.Start();
30        foreach (string s in ss) { str = s; b = str.IsNullOrEmpty1(); }
31        watch.Stop();
32        Console.Write("扩展方法:" + watch.ElapsedMilliseconds + "ms\t\t");
33        t1 += watch.ElapsedMilliseconds;
34
35        watch.Reset(); watch.Start();
36        foreach (string s in ss) { str = s; b = IsNullOrEmpty2(str); }
37        watch.Stop();
38        Console.Write("手工方法:" + watch.ElapsedMilliseconds + "ms\t\t");
39        t2 += watch.ElapsedMilliseconds;
40
41        watch.Reset(); watch.Start();
42        foreach (string s in ss) { str = s; b = IsNullOrEmpty3(str); }
43        watch.Stop();
44        Console.Write("lambda方法:" + watch.ElapsedMilliseconds + "ms\t\t");
45        t3 += watch.ElapsedMilliseconds;
46
47        Console.WriteLine();
48    }

49
50    Console.WriteLine();
51
52    Console.WriteLine(string.Format("扩展方法\t / 原方法\t = {0:f2}", (t1 - t) * 1.0 / (t0 - t)));
53    Console.WriteLine(string.Format("手工方法\t / 原方法\t = {0:f2}", (t2 - t) * 1.0 / (t0 - t)));
54    Console.WriteLine(string.Format("lambda方法\t / 原方法\t = {0:f2}", (t3 - t) * 1.0 / (t0 - t)));
55}

56
想重构一下,考虑了几种办法,不太好,怕重构后大家看起来更费力。

Test中的4个小段代码很相似,分别用来测量4个算法的用时。
1       foreach  ( string  s  in  ss) str  =  s;

上面这句代码是基本循环,后面三组代码都在它基础上加入相应操作。
Test()不复杂,就是太啰嗦,大家都看得明白。

先在Debug模式下执行测试:

后面三个方法效率也太低了吧!!且一放,再看Release模式:

比前面效率提高了一些。最后是把Release模式下生成的程序,放在命令行中执行:

说明一:项目的输出类型必需是“控制台应用程序”才能在控制台中输出。
说明二:控制台的宽度比较小,我删除了Test()中输出中的几个制表符等,才让它输入不换行。
说明三:本处执行的是Release模式生成的程序,而不是Debug模式生成的程序。

Debug和Release测试是在VS2008宿主中进行的,最后控制台测试才是真正的实际运行环境,我们测试结果以控制台测试结果为准
之所以将前面两个贴出来,是告诉大家在vs中调试测试的结果是相当不准确的

我们来分析下测试的结果吧:
1.扩展方法的效率是相当高的,与原方法只有百分之几(多运行几次,可能是1、3、4甚至0,还有一次是-2,即比值为0.98)的性能损失。
2.手工方法效率最低,低得出乎大多数人的意料。
3.lambda会带来“可观”的性能损失

如果考虑性能:可以使用扩展方法,但扩展方法内部不要使用lambda表达式,其内部尽量使用常规代码。
(其实扩展方法内部代码简洁与否无所谓,毕竟扩展方法是一种封装,可以将内部复杂的操作隐藏起来并以一个简单的扩展方法提供给调用者)
如果考虑性能:少用lambda,多用原生方法。

感觉:这次测试的结果令我倍感意外,确实没想到扩展方法的效率如此之高(看来我的扩展想法有市场了)!
期望:本人是“粗人”,很不细心,大家如果发现上面测试中有错误,请马上告知我,谢谢!
打算:对一个扩展方法的测试说服力不够,以后会再做一些相关测试工作。
感慨:效率的高低不是眼睛看看、脑子想想能断定的,而必需采用科学的测试方法才可以给出结论
(讨论,如果本文只给出在debug及release下的测试结果,会是怎样的呢?)


本人系列文章《c#扩展方法奇思妙用》,敬请关注!


推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • 开源Keras Faster RCNN模型介绍及代码结构解析
    本文介绍了开源Keras Faster RCNN模型的环境需求和代码结构,包括FasterRCNN源码解析、RPN与classifier定义、data_generators.py文件的功能以及损失计算。同时提供了该模型的开源地址和安装所需的库。 ... [详细]
author-avatar
别想着摆脱_525
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有