C#提升性能的几点提示和技巧

时间:2021-11-15 11:59:09

C#提升性能的几点提示和技巧

在Raygun[1],我们是一群非常懂多种语言的开发人员。Raygun的各个部分使用不同的语言和框架编写-最好的工作方式。

鉴于大量的C#和我们正在处理的数据的爆炸性增长,在不同的时间需要进行一些优化工作。大部分重大的收获往往来自于真正地重新思考问题并从全新的角度解决问题。

今天我想分享一些C#性能技巧,这些技巧对我的最新工作有所帮助。其中一些功能在你看来也许相当微不足道,因此请不要在这里充电并使用所有功能。就这样,提示1是…

1.每个开发人员都应使用分析器

有一些很棒的.NET分析器。我个人使用了Jet Brains[2]团队的dotTrace分析器。我知道我们团队中的Jason 也从Red Gate分析器中[3]获得了很多价值。每个开发人员都应安装并使用探查器。

我无法数出我认为应用程序的最慢部分在一个区域中的次数,而实际上却完全在其他地方。探查器对此提供了帮助。此外,有时候,它可以帮助我发现错误-缓慢的部分之所以缓慢,只是因为它做错了什么(单元测试

没有

正确地拾取它)。

这是您要执行的所有优化工作的第一步,也是有效的第一步。

2.抽象级别越高,速度越慢(通常)

这只是我闻到的气味。您使用的抽象级别越高,通常越慢。我在这里发现的一个常见示例是在代码繁忙的部分(也许在循环中被称为数百万次)中使用LINQ。LINQ非常适合快速表达某些内容,而这些内容可能要花一堆代码,但是您通常会将性能留在桌面上。

不要误会我的意思-LINQ非常适合让您开发出可运行的应用程序。但是在代码库中以性能为中心的部分中,您可能会付出太多。特别是因为将这么多操作链接在一起非常容易。

我所使用的特定示例是我使用的地方.SelectMany().Distinct().Count()。鉴于这被称为数千万次(由我的探查器发现的关键热点),它正在累积大量的运行时间。我采用了另一种方法,并将执行时间减少了几个数量级。

3.不要低估发行版和调试版

我一直在努力工作,对获得的性能感到非常满意。然后,我意识到自己已经在Visual Studio中进行了所有测试(我经常将性能测试编写为也可以作为单元测试运行,因此我可以更轻松地运行自己关心的部分)。我们都知道发行版本已启用优化。

因此,我做了一个发布版本,称为从控制台应用程序测试的方法。

我对此有了很大的转变。我的代码已经疯狂地进行了优化,因此确实是时候对.NET JIT编译器进行一些微优化了。启用优化后,我的性能提高了约30%!这使我想起了我不久前在网上阅读的一个故事。

这是上世纪90年代的一个古老游戏编程故事,当时内存限制非常严格。在开发周期的后期,团队最终将耗尽内存,并开始考虑必须删除或降级哪些内容以适合可用的微小内存空间。资深开发人员根据他的经验就曾期望这样做,并在项目一开始就分配了1MB的内存和垃圾数据。然后,他节省了一天的时间,并删除了他在项目开始时立即分配的1MB内存,从而解决了问题!

知道团队总是没有足够的空间,因为那里有可用的内存,就可以为团队提供他们所需要的东西,并按时发货。

我为什么要分享这个?在性能方面类似–在调试模式下获得足够好的运行,并且您将在发行版本中获得一些“免费”性能。美好时光。

4.看大局

有一些很棒的算法。您多数不需要每天甚至每月都不用。但是,值得知道它们的存在。我经常进行研究后,就会发现一种更好的解决问题的方法。在编码之前进行研究的开发人员与在编写代码之前进行适当分析的开发人员的可能性差不多。我们喜欢代码,并且总是想直接进入IDE。

此外,通常在查看性能问题时,我们过于专注于单个生产线或方法。这可能是一个错误–放眼全局,可以通过减少需要完成的工作来帮助您显着提高性能。

5.内存位置很重要

假设我们有一个数组数组。实际上是一张桌子,尺寸为3000×3000。我们要计算有多少个插槽的值大于零。

问题–这两个中哪个更快?

  1. for(inti=0;i<_map.Length;i++)
  2. {
  3. for(intn=0;n<_map.Length;n++)
  4. {
  5. if(_map[i][n]>0)
  6. {
  7. result++;
  8. }
  9. }
  10. }
  11. for(inti=0;i<_map.Length;i++)
  12. {
  13. for(intn=0;n<_map.Length;n++)
  14. {
  15. if(_map[n][i]>0)
  16. {
  17. result++;
  18. }
  19. }
  20. }

回答?第一个。在我的测试中,此循环使性能提高了8倍!

注意区别吗?这是我们遍历此数组数组的顺序([i] [n]与[n] [i])。即使我们从自己管理内存中抽象出来,内存局部性在.NET中的确很重要。

就我而言,这种方法被称为数百万次(准确地说是数亿次),因此我可以从中获得的任何性能都获得了可观的胜利。再次感谢我经常使用的分析器,以确保我专注于正确的地方!

6.减轻垃圾收集器的压力

C#/.NET具有垃圾回收功能。垃圾收集是确定哪些对象当前已过时并删除它们以释放内存中空间的过程。这意味着在C#中,与C ++之类的语言不同,您不必手动维护不再有用的对象的删除,即可声明其在内存中的空间。相反,垃圾收集器(GC)处理所有这些,因此您不必这样做。

问题是没有免费的午餐

问题是没有免费的午餐。收集过程本身会导致性能下降,因此您实际上并不希望GC一直收集。那么如何避免这种情况呢?

有许多有用的技术可以避免对GC施加太大压力[4]。在这里,我将只关注一个技巧:避免不必要的分配。这意味着要避免这样的事情:

  1. Listproducts=newList();
  2. products=productRepo.All();

第一行创建了一个完全无用的列表实例,因为下一行返回另一个实例并将其引用分配给变量。现在想象一下上面的两行是否在一个执行数千次的循环中?

上面的代码可能看起来像一个愚蠢的示例,但是我已经在生产中看到了这样的代码,而不仅仅是一次。不要只关注示例本身,而要关注一般建议。除非确实需要,否则不要创建对象。

由于GC在.NET中的工作方式(这是一个世代的GC流程),因此较旧的对象更有可能收集较新的对象。这意味着创建许多新的,短暂的对象可能会触发GC运行。

7.不要使用空的析构函数

标题说明了一切-请勿在类中添加空的析构函数。Finalize每个具有析构函数的类的条目都会添加到队列中。然后在调用析构函数时调用我们的老朋友GC来处理队列。空的析构函数意味着这一切都是徒劳的。

请记住,就性能而言,GC执行并不便宜,正如我们已经提到的。不要不必要地导致GC工作。

8.避免不必要的装箱和拆箱

装箱和拆箱就像垃圾回收一样,在性能方面很昂贵。因此,我们希望避免不必要地进行操作。但是他们在实践中会做什么?

装箱就像创建引用类型框并将值类型的值放入其中一样。换句话说,它包括将值类型转换为“对象”或该值类型实现的接口类型。取消装箱相反,它会打开包装盒并从其中提取值类型。为什么会有问题呢?

好吧,正如我们已经提到的,装箱和拆箱本身就是昂贵的过程。除此之外,当您装箱一个值时,您会在堆上创建另一个对象,这给GC带来了额外的压力(您已经猜到了!)。

那么,如何避免装箱和拆箱呢?

通常,您可以通过避免.NET(版本1.0)中早于泛型的API来做到这一点,因此,它们必须依赖于使用对象类型。例如,更喜欢通用集合,例如System.Collections.Generic.List,而不是System.Collections.ArrayList。

9.当心字符串连接

在C#/。NET中,字符串是不可变的。因此,每次执行一些看起来好像在更改字符串的操作时,它们都会创建一个新的字符串。这些操作包括类似的方法Replace和Substring,同时也串联。

提防串联大量字符串,尤其是在循环内部

因此,这里的技巧很简单-注意不要串联大量字符串,尤其是在循环内部。在这种情况下,请使用System.Text.StringBuilder类,而不要使用“ +”运算符。这样可以确保不会为连接的每个部分创建新实例。

10.随时关注C#的发展

最后,我们以非常笼统的建议作为结尾-请密切关注C#语言的更改和发展方式。C#团队不断提供可以对性能产生积极影响的新功能。

我们可以提到的一个最新示例是C#7中引入的ref[5] return 和ref locals[6]。这些新功能允许开发人员按引用返回并将引用存储在局部变量中。C#7.2引入了Span[7] 类型,从而可以对内存的连续区域进行类型安全的访问。

诸如此类的新功能和类型不太可能被大多数C#开发人员使用,但是它们无疑会对性能至关重要的应用程序产生影响,值得进一步了解。

C#性能很重要!

这只是我发现对提高.NET代码性能有用的几件事的集合-但是值得花时间检查代码以确保其性能。您的团队和客户将感谢您!

References

[1] Raygun: https://raygun.com/

[2] Jet Brains: https://www.jetbrains.com/

[3] Red Gate分析器中: http://www.red-gate.com/products/dotnet-development/ants-performance-profiler/

[4] 许多有用的技术可以避免对GC施加太大压力: https://michaelscodingspot.com/avoid-gc-pressure/

[5] ref: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/ref-returns

[6] 和ref locals: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/ref-returns

[7] Span: https://docs.microsoft.com/en-us/dotnet/api/system.span-1?view=netcore-3.0

原文链接:https://mp.weixin.qq.com/s/XvBK2PDbq2lyRzfL7YP2uQ