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

时间:2010-03-18 22:07:16

标签: c# .net optimization

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

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

  • 将多个字符串连接在一起时,请改用StringBuilder。请参阅底部的链接,了解相关信息。
  • 使用string.Compare比较两个字符串,而不是像string1.ToLower() == string2.ToLower()
  • 那样做

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

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

我可以看到,知道为什么提示有用以及应该应用的位置可能会有用。对于StringBuilder提示,我找到了很久以前在here on Jon Skeet's site处所做的帮助。

11 个答案:

答案 0 :(得分:106)

  

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

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

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

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

多年来,我被问过几十次我的“技巧和窍门”列表,人们可以使用它来优化他们的vbscript /他们的jscript /他们的活动服务器页面/他们的VB /他们的C#代码。我总是反对这一点。 强调“技巧和窍门”正是处理绩效的错误方法。这种方式导致代码难以理解,难以推理,难以维护,通常不会明显快于相应的直截了当的代码。

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

  • 设定有意义,可衡量,以客户为中心的目标。
  • 构建测试套件,在现实但可控且可重复的条件下,针对这些目标测试您的表现。
  • 如果这些套房显示您无法实现目标,请使用分析器等工具来确定原因。
  • 优化分析器识别为性能最差的子系统。对每个更改进行分析,以便您清楚地了解每个更改对性能的影响。
  • 重复,直到发生以下三种情况之一:(1)您达到目标并运送软件,(2)您将目标向下修改为您可以实现的目标,或者(3)您的项目因您无法满足而被取消目标。

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

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

答案 1 :(得分:45)

获得一个好的探查者。

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

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

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

答案 2 :(得分:21)

优化指南:

  1. 除非您需要
  2. ,否则不要这样做
  3. 如果在问题而不是开发人员处抛出新硬件更便宜,请不要这样做
  4. 除非您可以衡量生产等效环境中的变化,否则不要这样做
  5. 除非您知道如何使用CPU 内存分析器
  6. ,否则不要这样做
  7. 如果要使代码不可读或不可维护,请不要这样做
  8. 随着处理器继续加快,大多数应用程序的主要瓶颈不是CPU,而是带宽:片外存储器的带宽,磁盘带宽和带宽带宽。

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

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

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

答案 3 :(得分:16)

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

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

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

答案 4 :(得分:13)

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

答案 5 :(得分:9)

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

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

这样做10到20次,你就可以很好地了解优化可能会带来什么变化。

答案 6 :(得分:9)

如果您将方法识别为瓶颈,但您不知道该怎么做,那么您基本上就会陷入困境。

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

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

答案 7 :(得分:8)

人们对真正重要的事情有着有趣的想法。 Stack Overflow充满了一些问题,例如++ii++更“高效”。 Here's an example of real performance tuning,它与任何语言的程序基本相同。如果代码只是以某种方式编写“因为它更快”,那就是猜测。

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

答案 8 :(得分:6)

事实是,没有完美的优化代码。但是,您可以在已知的CPU类型(和计数),已知平台(Microsoft?{{3}上的已知系统(或系统集)上优化代码的特定部分 }?),已知的框架/ Mono版本,已知的CLI版本,已知的编译器版本(错误,规范更改,调整),已知的总内存和可用内存量,已知的程序集原点({{3磁盘?远程?),具有来自其他进程的已知后台系统活动。

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

答案 9 :(得分: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)算法在循环内部有一个循环。

答案 10 :(得分:2)

我并没有真正尝试优化我的代码,但有时我会经历并使用像反射器之类的东西来将我的程序恢复到源代码。有趣的是,我将错误与反射器输出的内容进行比较。有时我发现我以更复杂的形式做的事情被简化了。可能无法优化事物,但可以帮助我看到更简单的问题解决方案。