优化C#/ .NET程序的技巧

Bob*_*Bob 78 .net c# optimization

如今优化似乎是一种迷失的艺术.所有程序员都没有从代码中挤出每一盎司的效率吗?经常在雪地里行走五英里的时候这样做?

本着回归丢失的艺术的精神,您知道的简单(或复杂)变化以优化C#/ .NET代码的一些提示是什么?因为它是如此广泛,取决于一个人想要完成什么,它有助于提供你的提示的背景.例如:

  • 当连接多个字符串时,请使用StringBuilder.请参阅底部的链接以了解相关信息.
  • 使用string.Compare两个字符串比较,而不是做这样的事情string1.ToLower() == string2.ToLower()

到目前为止,普遍的共识似乎是衡量关键.这种方式忽略了这一点:测量不会告诉你什么是错的,或者如果遇到瓶颈会怎么做.我遇到了字符串连接瓶颈一次,不知道该怎么办,所以这些提示很有用.

我甚至发布这个问题的意思是为了解决常见的瓶颈问题,以及在遇到这些问题之前如何避免它们.它甚至不一定是任何人应该盲目遵循的即插即用代码,而是更多关于获得对性能应该被考虑的理解,至少在某种程度上,并且需要注意一些常见的陷阱.

我可以看到,知道为什么提示有用以及应该应用的位置可能会有用.对于StringBuilder小费,我找到了很久以前在Jon Skeet网站上做过的帮助.

Eri*_*ert 106

如今优化似乎是一种迷失的艺术.

每天都有一次制造显微镜作为艺术品.光学原理知之甚少.部件没有标准化.管子,齿轮和镜片必须由高技术工人手工制作.

目前,显微镜是作为工程学科生产的.物理学的基本原理非常容易理解,现成的部件可以广泛使用,显微镜制造工程师可以做出明智的选择,以便如何最好地优化仪器以完成其设计的任务.

那种表现分析是一种"失落的艺术",是一件非常非常好的事情.这种艺术被作为一门艺术来实践.应该优化它的优化:通过仔细应用可靠的工程原理来解决工程问题.

我一直在问数十多年来多次为我的人们可以用它来优化自己的VBScript/JScript中的/他们的Active Server Pages /他们VB /他们的C#代码"的技巧和窍门"名单.我总是反对这一点.强调"提示和技巧"正是处理绩效的错误方法. 这种方式导致代码难以理解,难以推理,难以维护,通常不会明显快于相应的直接代码.

接近性能的正确方法是将其视为工程问题,就像任何其他问题一样:

  • 设定有意义,可衡量,以客户为中心的目标.
  • 构建测试套件,在现实但受控且可重复的条件下,针对这些目标测试您的性能.
  • 如果这些套件显示您无法实现目标,请使用分析器等工具来确定原因.
  • 优化分析器识别为性能最差的子系统.对每个更改进行分析,以便您清楚地了解每个更改的性能影响.
  • 重复,直到三件事情发生对象(1)您达成目标,并在销售软件,(2)您调低自己的目标的东西就可以实现,或者(3)你的项目被取消了,因为你不能达到你的目标.

这与您解决任何其他工程问题相同,例如添加功能 - 为功能设置以客户为中心的目标,跟踪实现可靠实施的进度,通过仔细调试分析找到问题,并继续迭代直到你发货或失败.性能是一个功能.

对复杂的现代系统进行性能分析需要遵守规则并专注于坚实的工程原则,而不是一揽子,这些技巧很少适用于琐碎或不切实际的情况.我从来没有通过应用提示和技巧解决现实世界的性能问题.

  • 在某些情况下,有一种已知的更好的方法可以完成相同的任务,同时减少对资源的负担.我不认为只要你达到某个目标就可以编程完全没问题.或者它是最好的编程,*然后*运行一个分析器,然后*然后*返回并更改问题区域.一个人知道如何优化某些代码甚至在它们启动之前需要做什么有什么问题呢? (7认同)
  • @Bob:我同意一些程序员不关心性能.但我不是在遵循你的观点.提示和技巧列表不会突然变成关心性能的人.假设为了论证你*可以*使目前对感兴趣的人不感兴趣的人,一系列提示和技巧不会帮助他们获得良好的表现.您可以整天将代码和技巧应用到代码体系中,并且永远不知道您是否根据目标取得任何进展.你必须有目标并衡量你的进步. (7认同)
  • @Bob:聪明地使用资源没有错.出问题的地方是人们(1)花费大量时间(=金钱)进行微观优化而没有区别,(2)编写*错误的程序*,以及(3)编写不清楚的程序.你应该优化的是第一,正确.第二,良好的编码风格.第三,表现.一旦代码正确和优雅,它将更容易使其高效. (5认同)
  • 那没关系,但你会注意到我并不是说不应该首先编码正确性,或者说第二种,或者你有什么.但是,有时(或者现在可能很多次),程序员根本不考虑性能或优化也是如此.只有1和2足以弥补3的总体不关心?我看不出如何在优化方面表达一些不好的想法,并了解它需要做些什么 (3认同)

Ree*_*sey 45

得到一个好的探查者.

在没有好的分析器的情况下,即使尝试优化C#(实际上是任何代码),也不要打扰.实际上,同时拥有采样和跟踪分析器实际上有很大帮助.

如果没有一个好的分析器,您可能会创建错误的优化,最重要的是,首先优化不是性能问题的例程.

分析的前三个步骤应始终是1)测量,2)测量,然后3)测量....

  • 你忘记了'4)措施 (22认同)

Ian*_*cer 21

优化指南:

  1. 除非你需要,否则不要这样做
  2. 如果在问题而不是开发人员处抛出新硬件更便宜,就不要这样做
  3. 除非您可以衡量生产等效环境中的变化,否则不要这样做
  4. 除非您知道如何使用CPU 内存分析器,否则不要这样做
  5. 如果它会使您的代码无法读取或无法维护,请不要这样做

随着处理器继续加快,大多数应用程序的主要瓶颈不是CPU,而是带宽:片外存储器带宽,磁盘带宽和带宽带宽.

从远端开始:使用YSlow查看为什么您的网站对最终用户来说速度慢,然后返回并修复数据库访问不要太宽(列)而不是太深(行).

在非常罕见的情况下,值得采取任何措施来优化CPU使用率,请注意不要对内存使用产生负面影响:我已经看到"优化",开发人员试图使用内存来缓存结果以节省CPU周期.最终效果是减少可用内存以缓存页面和数据库结果,这使得应用程序运行得更慢!(参见关于测量的规则.)

我也看到了一个'哑'未优化算法击败'聪明'优化算法的情况.永远不要低估编译器编写者和芯片设计人员如何将"低效"循环代码转换为可以完全在片上内存中运行流水线的超高效代码.你的"聪明"的基于树的算法带有一个你认为"有效"的向后计数的内包循环,可以被打败,因为它在执行过程中无法留在片上内存中.(参见关于测量的规则.)

  • 同样,不要痴迷于大O分析.对于常见的商业案例,O(nm)朴素字符串搜索算法比预处理搜索字符串寻找模式的O(n + m)算法快数千倍.与第一个字符匹配的朴素字符串搜索通常编译为单个机器指令,这对于大量使用乐观内存缓存的现代处理器来说非常快. (10认同)

Aar*_*ron 16

使用ORM时,请注意N + 1选择.

List<Order> _orders = _repository.GetOrders(DateTime.Now);
foreach(var order in _orders)
{
    Print(order.Customer.Name);
}
Run Code Online (Sandbox Code Playgroud)

如果客户没有急切加载,这可能会导致数次往返数据库.


Sof*_*eek 13

  • 不要使用幻数,使用枚举
  • 不要硬编码值
  • 尽可能使用泛型,因为它是类型安全的并且避免装箱和拆箱
  • 在绝对需要的地方使用错误处理程序
  • 配置,处置,配置.CLR不知道如何关闭数据库连接,因此在使用后关闭它们并处置非托管资源
  • 使用常识!

  • 尽管我同意他们是好事,但前两件事对性能没有影响 - 只是可维护性...... (15认同)
  • 此外,第3(拳击)很少是一个真正的夹点; 它被夸大为一个问题; 例外情况 - 通常不是*问题. (4认同)

Con*_*cht 9

好吧,我必须抛弃我最喜欢的:如果任务足够长时间进行人工交互,请在调试器中使用手动中断.

比.一个分析器,它为您提供了一个调用堆栈和变量值,您可以使用它们来真正了解正在发生的事情.

这样做10-20次,你就会明白优化可能会带来什么样的改变.

  • 这本质上是剖析器所做的,除了它们以大约一千种不同的方式(更快,更频繁,更准确等)比你更好.他们也会给出呼叫堆栈.这是穷人(以及老人,他们害怕学习新东西)的解决方案. (5认同)
  • ++阿门。自从存在分析器之前,我就一直在这样做。和您的程序 DrawMusic 看起来棒极了! (2认同)
  • @ BlueRaja-DannyPflughoeft:我相信你不会,当你达到我的年龄时,你会遇到像你这样的人.但是让我们把它放在一边.[*这里有一些源代码*](http://sourceforge.net/projects/randompausedemo/)如果你可以加速3个数量级,而不用看我是怎么做的,使用任何其他方法,你将有吹牛的权利 :) (2认同)

har*_*old 9

如果您将方法识别为瓶颈,但您不知道如何处理它,那么您基本上就会陷入困境.

所以我列出一些东西.所有这些都不是银子弹,你仍然需要分析你的代码.我只是做的东西,你的建议可以做,有时可以帮助.特别是前三个很重要.

  • 尝试使用(或:主要)低级类型或它们的数组来解决问题.
  • 问题通常很小 - 使用智能但复杂的算法并不总能让你获胜,特别是如果不太智能的算法可以用仅使用(数组)低级类型的代码表示.举例来说,InsertionSort vs MergeSort的n <= 100或Tarjan的Dominator查找算法与使用bitvectors来天真地解决n <= 100的问题的数据流形式.(100当然只是为了给你一些想法 - 个人资料!)
  • 考虑编写一个可以使用低级类型(通常是大小<64的问题实例)解决的特殊情况,即使您必须为更大的问题实例保留其他代码.
  • 学习按位算术来帮助您完成上述两个想法.
  • 与Dictionary相比,BitArray可以是你的朋友,或者更糟糕的是List.但请注意,实施并非最佳; 您可以自己编写更快的版本.您可以经常构建算法,而不是测试您的参数是否超出范围等,以便索引无论如何也不会超出范围 - 但是您无法从标准BitArray中删除该检查并且它不是免费的.
  • 作为只使用低级类型数组可以做什么的一个例子,BitMatrix是一个相当强大的结构,可以实现为一个ulongs数组,你甚至可以使用ulong作为"前面"来遍历它,因为你可以采取恒定时间内的最低位(与广度优先搜索中的队列相比 - 但显然顺序不同,取决于项目的索引而不是纯粹的查找顺序).
  • 除非右侧是常数,否则除法和模数非常慢.
  • 浮点数学通常不会比整数数学慢(不是"你可以做的事情",而是"你可以跳过去做的事情")
  • 分支不是免费的.如果你可以使用简单的算术(除了除法或模数之外的任何东西)来避免它,你有时可以获得一些性能.将分支移动到循环外部几乎总是一个好主意.


Mik*_*vey 8

人们对真正重要的事情有很好的想法.Stack Overflow充满了一些问题,例如,++i比"高效" i++.这是一个真正的性能调优的例子,它基本上与任何语言相同.如果代码只是以某种方式编写"因为它更快",那就是猜测.

当然,你并不是故意编写愚蠢的代码,但如果猜测有效,就不需要分析器和分析技术.


Mar*_*ell 6

事实是,没有完美的优化代码.但是,您可以在已知系统(或一组系统)上针对已知CPU类型(和计数),已知平台(Microsoft?Mono?),已知框架/ BCL版本优化代码的特定部分,已知的CLI版本,已知的编译器版本(错误,规范更改,调整),已知数量的总和可用内存,已知的程序集源(GAC?disk?remote?),具有来自其他进程的已知后台系统活动.

在现实世界中,使用分析器,并查看重要的位; 通常明显的事情是涉及I/O的任何事情,涉及线程的任何事情(再次,这在版本之间发生巨大变化),以及涉及循环和查找的任何事情,但你可能会惊讶于"明显不好"的代码实际上不是一个问题,什么"明显好"的代码是一个巨大的罪魁祸首.


Gab*_*abe 5

告诉编译器是什么做的,而不是如何去做.例如,foreach (var item in list)优于for (int i = 0; i < list.Count; i++)并且m = list.Max(i => i.value);优于list.Sort(i => i.value); m = list[list.Count - 1];.

通过告诉系统您想要做什么,它可以找出最佳方法.LINQ很好,因为在您需要它之前不会计算结果.如果您只使用第一个结果,则不必计算其余结果.

最终(这适用于所有编程)最小化循环并最小化您在循环中执行的操作.更重要的是最小化循环内的循环次数.O(n)算法和O(n ^ 2)算法之间有什么区别?O(n ^ 2)算法在循环内部具有循环.