热门标签 | 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#扩展方法奇思妙用》,敬请关注!


推荐阅读
  • 开发笔记:9.八大排序
    开发笔记:9.八大排序 ... [详细]
  • 在软件开发过程中,MD5加密是一种常见的数据保护手段。本文将详细介绍如何在C#中使用两种不同的方式来实现MD5加密:字符串加密和流加密。 ... [详细]
  • JavaScript 基础语法指南
    本文详细介绍了 JavaScript 的基础语法,包括变量、数据类型、运算符、语句和函数等内容,旨在为初学者提供全面的入门指导。 ... [详细]
  • Java 实现二维极点算法
    本文介绍了一种使用 Java 编程语言实现的二维极点算法。该算法用于从一组二维坐标中筛选出极点,适用于需要处理几何图形和空间数据的应用场景。文章不仅详细解释了算法的工作原理,还提供了完整的代码示例。 ... [详细]
  • 本文探讨了在Java中如何正确地将多个不同的数组插入到ArrayList中,避免所有数组在插入后变得相同的问题。我们将分析代码中的问题,并提供解决方案。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • Ihaveastringwithquotesaroundthepathasfollows:我在路径周围有一个带引号的字符串,如下所示:C:\ProgramFiles(x ... [详细]
  • 本文探讨了在C++中如何有效地清空输入缓冲区,确保程序只处理最近的输入并丢弃多余的输入。我们将介绍一种不阻塞的方法,并提供一个具体的实现方案。 ... [详细]
  • 深入解析Redis内存对象模型
    本文详细介绍了Redis内存对象模型的关键知识点,包括内存统计、内存分配、数据存储细节及优化策略。通过实际案例和专业分析,帮助读者全面理解Redis内存管理机制。 ... [详细]
  • 本文探讨了使用C#在SQL Server和Access数据库中批量插入多条数据的性能差异。通过具体代码示例,详细分析了两种数据库的执行效率,并提供了优化建议。 ... [详细]
  • 深入理解Lucene搜索机制
    本文旨在帮助读者全面掌握Lucene搜索的编写步骤、核心API及其应用。通过详细解析Lucene的基本查询和查询解析器的使用方法,结合架构图和代码示例,带领读者深入了解Lucene搜索的工作流程。 ... [详细]
  • 深入解析for与foreach遍历集合时的性能差异
    本文将详细探讨for循环和foreach(迭代器)在遍历集合时的性能差异,并通过实际代码示例和源码分析,帮助读者理解这两种遍历方式的不同之处。文章内容丰富且专业,旨在为编程爱好者提供有价值的参考。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 本文深入探讨了SQL数据库中常见的面试问题,包括如何获取自增字段的当前值、防止SQL注入的方法、游标的作用与使用、索引的形式及其优缺点,以及事务和存储过程的概念。通过详细的解答和示例,帮助读者更好地理解和应对这些技术问题。 ... [详细]
  • 本题来自WC2014,题目编号为BZOJ3435、洛谷P3920和UOJ55。该问题描述了一棵不断生长的带权树及其节点上小精灵之间的友谊关系,要求实时计算每次新增节点后树上所有可能的朋友对数。 ... [详细]
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社区 版权所有