Tips for optimizing C#/.NET programs

It seems like optimization is a lost art these days. Wasn't there a time when all programmers squeezed every ounce of efficiency from their code? Often doing so while walking five miles in the snow?

In the spirit of bringing back a lost art, what are some tips that you know of for simple (or perhaps complex) changes to optimize C#/.NET code? Since it's such a broad thing that depends on what one is trying to accomplish it'd help to provide context with your tip. For instance:

  • When concatenating many strings together use StringBuilder instead. See link at the bottom for caveats on this.
  • Use string.Compare to compare two strings instead of doing something like string1.ToLower() == string2.ToLower()

The general consensus so far seems to be measuring is key. This kind of misses the point: measuring doesn't tell you what's wrong, or what to do about it if you run into a bottleneck. I ran into the string concatenation bottleneck once and had no idea what to do about it, so these tips are useful.

My point for even posting this is to have a place for common bottlenecks and how they can be avoided before even running into them. It's not even necessarily about plug and play code that anyone should blindly follow, but more about gaining an understanding that performance should be thought about, at least somewhat, and that there's some common pitfalls to look out for.

I can see though that it might be useful to also know why a tip is useful and where it should be applied. For the StringBuilder tip I found the help I did long ago at here on Jon Skeet's site.

39011 次浏览

找个好的侧写师。

如果没有一个好的分析器,就不要尝试优化 C # (实际上,任何代码)。实际上,手头同时拥有一个采样和一个跟踪分析器非常有帮助。

如果没有一个好的分析器,您可能会创建错误的优化,并且,最重要的是,优化例程,而不是性能问题摆在首位。

分析的前三个步骤应该是: 1)度量,2)度量,然后3)度量... ..。

人们对于什么才是真正重要的东西总是有些奇怪的想法。StackOverflow 充满了关于 ++ii++更“性能”的问题。这是一个真正的性能调优,对于任何语言它基本上都是相同的过程。如果代码只是简单地以某种方式编写,“因为它更快”,那就是猜测。

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

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

事实上,根本就没有完美优化代码这回事。然而,您可以针对特定的 部分代码、已知的系统(或系统集)、已知的 CPU 类型(和计数)、已知的平台(Microsoft?单核细胞增多症?)一个已知的框架/BCL版本,一个已知的 CLI 版本,一个已知的编译器版本(bug,规范更改,调整) ,一个已知的总内存和可用内存的数量,一个已知的程序集起源(GAC?磁盘?遥控器?),具有来自其他进程的已知后台系统活动。

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

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

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

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

如今,优化似乎是一门失传的艺术。

曾经有一天,显微镜的制造被当作一门艺术来实践。光学原理知之甚少。没有标准化的零件。这些管子、齿轮和镜片必须由技术高超的工人手工制作。

现在显微镜作为一门工程学科被生产出来。物理学的基本原理人们已经非常了解,现成的零部件随处可见,显微镜制造工程师可以在知情的情况下选择如何最佳地优化他们的仪器来完成设计要完成的任务。

表现分析是一门“失传的艺术”,这是一件非常非常好的事情。这门艺术是练习过的。最优化应该接近它是什么: 一个 工程问题可以通过仔细应用扎实的工程原则解决。

I have been asked dozens of times over the years for my list of "tips and tricks" that people can use to optimize their vbscript / their jscript / their active server pages / their VB / their C# code. I always resist this. 强调“技巧和窍门”是完全错误的方式来接近性能。 That way leads to code which is hard to understand, hard to reason about, hard to maintain, that is typically not noticably faster than the corresponding straightforward code.

The right way to approach performance is to approach it as an engineering problem like any other problem:

  • Set meaningful, measurable, customer-focused goals.
  • 构建测试套件,以便在现实的、可控的和可重复的条件下,针对这些目标测试您的性能。
  • 如果这些套件显示您没有达到目标,请使用分析器等工具来找出原因。
  • 优化探查器认为表现最差的子系统。对每个更改进行概要分析,以便清楚地了解每个更改对性能的影响。
  • 不断重复,直到三件事情中的一件发生(1)你完成了你的目标并且发布了软件; (2)你将你的目标向下修改到你能够完成的程度; 或者(3)你的项目因为你无法完成你的目标而被取消。

这和你解决其他任何工程问题是一样的,比如增加一个功能——为功能设定以客户为中心的目标,跟踪实现的进度,通过仔细的调试分析解决你发现的问题,不断迭代直到发布或失败。性能是一个特征。

对于复杂的现代系统的性能分析需要纪律,需要关注可靠的工程原则,而不是仅仅适用于琐碎或不现实情况的技巧。我从来没有通过应用提示和技巧来解决现实世界中的性能问题。

告诉编译器 什么去做,而不是 怎么做去做。例如,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)算法在循环中有一个循环。

优化指南:

  1. 不到万不得已不要这么做
  2. 如果抛出新的硬件而不是开发人员来解决问题会更便宜,那就不要这样做
  3. 除非您能够在与生产等效的环境中度量变化,否则不要这样做
  4. 不要这样做,除非你知道如何使用一个 CPU 还有一个内存分析器
  5. 如果这样做会使代码不可读或不可维护,就不要这样做

随着处理器不断提高速度,大多数应用程序的主要瓶颈不是 CPU,而是带宽: 到芯片外存储器的带宽、到磁盘的带宽和到网络的带宽。

从远端开始: 使用 YSlow 查看终端用户访问网站的速度为何较慢,然后后退并修复数据库访问不要太宽(列)和不要太深(行)。

在非常罕见的情况下,为了优化 CPU 使用而做任何事情都是值得的,但是要小心不要对内存使用产生负面影响: 我见过一些“优化”,开发人员试图使用内存来缓存结果,以节省 CPU 周期。最终的结果是减少了缓存页面和数据库结果的可用内存,这使得应用程序运行得更慢!(见测量规则。)

我也见过这样的例子,一个“哑巴”的未优化算法击败了一个“聪明”的优化算法。永远不要低估优秀的编译器编写人员和芯片设计人员在将“低效”的循环代码转化为超级高效的代码方面的能力,这些代码可以通过流水线在芯片内存中完全运行。你的“聪明的”基于树的算法有一个未包装的内部循环,你认为向后计数是“有效的”可以被击败,只是因为它没有留在芯片内存执行期间。(见测量规则。)

OK, I have got to throw in my favorite: If the task is long enough for human interaction, use a manual break in the debugger.

与分析器相比,这给了您一个调用堆栈和变量值,您可以使用它们来真正理解正在发生的事情。

这样做10-20次,你就会明白什么样的优化可能真的会产生影响。

我并没有真正尝试优化我的代码,但有时我会通过并使用一些像反射器把我的程序放回源代码。有趣的是,然后比较什么我错了什么反射器将输出。有时候我发现我所做的事情以一种更复杂的形式被简化了。可能不会优化事情,但帮助我看到更简单的问题解决方案。

如果您将一个方法确定为瓶颈,而 你不知道该怎么办却是瓶颈,那么您实际上就被卡住了。

我会列出一些事情。所有这些都是 not silver bullets和你 还是要侧写你的代码。我只是对你的 could做的事情提出建议,有时可以帮助。尤其是前三个很重要。

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