本文转载自微信公众号“DotNET技术圈”,作者Raygun。转载本文请联系DotNET技术圈公众号。在Raygun[1],我们是一群通晓多种语言的开发人员。Raygun的各个部分是用不同的语言和框架编写的——效果最好。鉴于C#的数量和我们正在处理的数据的爆炸式增长,在不同的时间需要进行一些优化工作。大多数巨大的收获往往来自真正重新思考问题并从全新的角度来解决问题。今天我想分享一些C#性能技巧,这些技巧对我最近的工作有所帮助。其中一些功能对您来说可能显得微不足道,所以不要在这里收费并全部使用。就是这样,技巧1是...1.每个开发人员都应该使用探查器有一些很棒的.NET探查器。我个人使用JetBrains[2]团队的dotTrace分析器。我知道我们团队的Jason也从RedGate分析器[3]中获得了很多价值。每个开发人员都应该安装和使用分析器。我数不清有多少次我认为应用程序的最慢部分在一个区域,但实际上完全在其他地方。分析器可以帮助解决这个问题。此外,有时,它可以帮助我找到错误——缓慢的部分之所以缓慢,是因为它做错了什么(单元测试没有正确地发现它)。这是您将执行的任何优化工作的第一步,也是有效的第一步。2.抽象层次越高,越慢(通常)这对我来说只是一种气味。您使用的抽象级别越高,它通常越慢。我在这里发现的一个常见示例是在代码的繁忙部分使用LINQ(可能在循环中调用了数百万次)。LINQ非常适合快速表达可能需要大量代码的内容,但您通常会将性能搁置一旁。不要误会我的意思——LINQ非常适合让您进入工作应用程序。但是在你的代码库中以性能为中心的部分,你可能付出了太多。特别是因为将如此多的操作链接在一起非常容易。我使用的具体示例是我使用.SelectMany().Distinct().Count()的地方。鉴于这被调用了数千万次(我的探查器发现了一个关键热点),它正在积累大量的运行时间。我采用了另一种方法,将执行时间减少了几个数量级。3.不要低估我一直在努力工作的发布和调试版本,并且对我获得的性能感到非常满意。然后我意识到我在VisualStudio中完成了所有测试(我经常编写我的性能测试也作为单元测试运行,因此我可以更轻松地运行我关心的部分)。我们都知道发行版启用了优化。所以我从控制台应用程序制作了一个称为测试方法的发布版本。我对此有了很大的转变。我的代码已经疯狂地优化过,所以是时候用.NETJIT编译器做一些微优化了。启用优化后,我的性能提高了大约30%!这让我想起了前段时间在网上看到的一个故事。这是90年代的一个古老的游戏编程故事,当时内存限制非常严格。在开发周期的后期,团队最终会耗尽内存并开始考虑必须删除或降级哪些内容以适应可用的微小内存空间。一位经验丰富的开发人员在他的经验中预料到了这一点,并在项目开始时分配了1MB的内存和垃圾数据。然后他化险为夷,删除了他在项目开始时立即分配的1MB内存,这样就解决了问题!知道团队总是没有足够的空间,因为有可用的内存,为团队提供了他们所需要的东西并按时交付。我为什么要分享这个?在性能方面类似——在调试模式下运行得足够好,您将在发布版本中获得一些“免费”性能。美好时光。4.有一些很棒的算法可以看大局。你们中的大多数人不需要每天甚至每个月都需要它。但是,值得知道它们的存在。通常在我进行研究之后,我会发现解决问题的更好方法。在编码之前进行研究的开发人员与在编写代码之前进行适当分析的开发人员的可能性差不多。我们喜欢编码,并且总是想直接进入IDE。此外,在查看性能问题时,我们常常过于关注单行或方法。这可能是一个错误——放眼大局可以通过减少需要完成的工作来帮助您显着提高性能。5.内存位置很重要假设我们有一个数组数组。实际上是一张尺寸为3000x3000的桌子。我们想计算有多少槽的值大于零。问题——这两个哪个更快?for(inti=0;i<_map.Length;i++){for(intn=0;n<_map.Length;n++){if(_map[i][n]>0){result++;}}}for(inti=0;i<_map.Length;i++){for(intn=0;n<_map.Length;n++){if(_map[n][i]>0){result++;}}}答案?第一的。在我的测试中,这个循环将性能提高了8倍!注意到区别了吗?这是我们遍历这个数组数组的顺序([i][n]与[n][i])。即使我们从自己管理内存中抽象出来,内存局部性在.NET中也很重要。在我的例子中,这个方法被调用了数百万次(准确地说是数亿次),所以我能从中获得的任何性能都是可靠的胜利。再次感谢我经常使用的分析器,以确保我专注于正确的事情!6.减少垃圾收集器的压力C#/.NET有垃圾收集功能。垃圾收集是确定哪些对象当前已过时并删除它们以释放内存空间的过程。这意味着在C#中,与C++之类的语言不同,您不必手动维护删除不再有用的对象以声明它们在内存中的空间。相反,垃圾收集器(GC)会处理所有这些,因此您不必这样做。问题是天下没有免费的午餐。问题是天下没有免费的午餐。收集过程本身会导致性能下降,因此您真的不希望GC一直收集。那么如何避免这种情况呢?有许多有用的技术可以避免对GC施加太大压力[4]。在这里,我只关注一个技巧:避免不必要的分配。这意味着要避免这样的事情:List
