Lambda表达式:要性能还是要清晰的代码?
创始人
2024-04-27 08:51:11
0次

在上文的***个示例中,我们演示了如何使用Lambda表达式配合.NET 3.5中定义的扩展方法来方便地处理集合中的元素(筛选,转化等等)。不过有朋友可能会提出,那个“普通写法”并非是性能***的实现方法。方便起见,也为了突出“性能”方面的问题,我们把原来的要求简化一下:将序列中的偶数平方输出为一个列表。按照那种“普通写法”可能就是:

  1. static List< int> EvenSquare(IEnumerable< int> source)  
  2. {  
  3.     var evenList = new List< int>();  
  4.     foreach (var i in source)  
  5.     {  
  6.         if (i % 2 == 0) evenList.Add(i);  
  7.     }  
  8.  
  9.     var squareList = new List< int>();  
  10.     foreach (var i in evenList) squareList.Add(i * i);  
  11.  
  12.     return squareList;  
  13. }  
  14.  

从理论上来说,这样的写法的确比以下的做法在性能要差一些:

  1. static List< int> EvenSquareFast(IEnumerable< int> source)  
  2. {  
  3.     List< int> result = new List< int>();  
  4.     foreach (var i in source)  
  5.     {  
  6.         if (i % 2 == 0) result.Add(i * i);  
  7.     }  
  8.  
  9.     return result;  
  10. }  
  11.  

在第二种写法直接在一次遍历中进行筛选,并且直接转化。而***种写法会则根据“功能描述”将做法分为两步,先筛选后转化,并使用一个临时列表进行保存。在向临时列表中添加元素的时候,List< int>可能会在容量不够的时候加倍并复制元素,这便造成了性能损失。虽然我们通过“分析”可以得出结论,不过实际结果还是使用CodeTimer来测试一番比较妥当:

  1. List< int> source = new List< int>();  
  2. for (var i = 0; i <  10000; i++) source.Add(i);  
  3.  
  4. // 预热  
  5. EvenSquare(source);  
  6. EvenSquareFast(source);  
  7.  
  8. CodeTimer.Initialize();  
  9. CodeTimer.Time("Normal", 10000, () => EvenSquare(source));  
  10. CodeTimer.Time("Fast", 10000, () => EvenSquareFast(source));  
  11.  

我们准备了一个长度为10000的列表,并使用EvenSquare和EvenSquareFast各执行一万次,结果如下:

  1. Normal  
  2.         Time Elapsed:   3,506ms  
  3.         CPU Cycles:     6,713,448,335  
  4.         Gen 0:          624  
  5.         Gen 1:          1  
  6.         Gen 2:          0  
  7.  
  8. Fast  
  9.         Time Elapsed:   2,283ms  
  10.         CPU Cycles:     4,390,611,247  
  11.         Gen 0:          312  
  12.         Gen 1:          0  
  13.         Gen 2:          0  

Lambda表达式的执行:性能比对与结论

结果同我们料想中的一致,EvenSquareFast无论从性能还是GC上都领先于EvenSquare方法。不过,在实际情况下,我们该选择哪种做法呢?如果是我的话,我会倾向于选择EvenSquare,理由是“清晰”二字。

EvenSquare虽然使用了额外的临时容器来保存中间结果(因此造成了性能和GC上的损失),但是它的逻辑和我们需要的功能较为匹配,我们可以很容易地看清代码所表达的含义。至于其中造成的性能损失在实际项目中可以说是微乎其微的。因为实际上我们的大部分性能是消耗在每个步骤的功能上,例如每次Int32.Parse所消耗的时间便是一个简单乘法的几十甚至几百倍。因此,虽然我们的测试体现了超过50%的性能差距,不过由于这只是“纯遍历”所消耗的时间,因此如果算上每个步骤的耗时,性能差距可能就会变成10%,5%甚至更低。

当然,如果是如上述代码那样简单的逻辑,则使用EvenSquareFast这样的实现方式也没有任何问题。事实上,我们也不必强求将所有步骤完全合并(即仅仅使用1次循环)或完全分开。我们可以在可读性与性能之间寻求一种平衡,例如将5个步骤使用两次循环来完能是更合适的方式。

说到“分解循环”,其实这类似于Martin Fowler在他的重构网站所上列出的重构方式之一:“Split Loop”。虽然Split Loop和我们的场景略有不同,但是它也是为了代码的可读性而避免将多种逻辑放在一个循环内。将循环拆开之后,还可以配合“Extract Method”或“Replace Temp with Query”等方式实现进一步的重构。自然,它也提到拆分后的性能影响:

You often see loops that are doing two different things at once, because they can do that with one pass through a loop. Indeed most programmers would feel very uncomfortable with this refactoring as it forces you to execute the loop twice - which is double the work.

But like so many optimizations, doing two different things in one loop is less clear than doing them separately. It also causes problems for further refactoring as it introduces temps that get in the way of further refactorings. So while refactoring, don't be afraid to get rid of the loop. When you optimize, if the loop is slow that will show up and it would be right to slam the loops back together at that point. You may be surprised at how often the loop isn't a bottleneck, or how the later refactorings open up another, more powerful, optimization.

这段文字提到,当拆分之后,您可能会发现更好的优化方式。高德纳爷爷也认为“过早优化是万恶之源”。这些说法都在“鼓励”我们将程序写的更清晰而不是“看起来”更有效率。

以上便分析了使用C# Lambda表达式编码时需要优先考虑代码清晰度的理由。

【编辑推荐】

  1. .NET Lambda表达式的函数式特性:索引示例
  2. .NET Lambda表达式的语义:字符串列表范例
  3. 使用.NET 3.5 Lambda表达式实现委托
  4. 各版本.NET委托的写法回顾
  5. C# Actor模型开发实例:网络爬虫

相关内容

热门资讯

PHP新手之PHP入门 PHP是一种易于学习和使用的服务器端脚本语言。只需要很少的编程知识你就能使用PHP建立一个真正交互的...
网络中立的未来 网络中立性是什... 《牛津词典》中对“网络中立”的解释是“电信运营商应秉持的一种原则,即不考虑来源地提供所有内容和应用的...
各种千兆交换机的数据接口类型详... 千兆交换机有很多值得学习的地方,这里我们主要介绍各种千兆交换机的数据接口类型,作为局域网的主要连接设...
粉嫩如何诠释霸道 东芝M805... “霸道粉”是个什么玩意东芝M805拿过来的时候,笔者扑哧笑了,不是笑这款笔记本,而是笑这款产品的颜色...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
什么是大数据安全 什么是大数据... 在《为什么需要大数据安全分析》一文中,我们已经阐述了一个重要观点,即:安全要素信息呈现出大数据的特征...
全面诠释网络负载均衡 负载均衡的出现大大缓解了服务器的压力,更是有效的利用了资源,提高了效率。那么我们现在来说一下网络负载...
如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
30分钟搞定iOS自定义相机 最近公司的项目中用到了相机,由于不用系统的相机,UI给的相机切图,必须自定义才可以。就花时间简单研究...
Windows恶意软件20年“... 在Windows的早期年代,病毒游走于系统之间,偶尔删除文件(但被删除的文件几乎都是可恢复的),并弹...