当前位置: 首页 > 科技观察

C#提高性能的一些技巧和技巧

时间:2023-03-14 12:43:43 科技观察

本文转载自微信公众号“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]。在这里,我只关注一个技巧:避免不必要的分配。这意味着要避免这样的事情:Listproducts=newList();products=productRepo.All();第一行创建一个完全无用的List实例,因为下一行返回另一个实例并将其引用分配给一个变量。现在想象一下,如果上面两行是在一个执行了数千次的循环中?上面的代码可能看起来像一个愚蠢的例子,但我在生产中见过它,不止一次。不要只关注示例本身,而要关注一般建议。除非确实需要,否则不要创建对象。由于.NET中GC的工作方式(它是分代GC过程),较旧的对象更有可能被较新的对象收集。这意味着创建许多新的、短暂的对象可能会触发GC运行。7.不要使用空的析构函数标题说明了一切——不要在你的类中添加空的析构函数。将具有析构函数的每个类的最终条目添加到队列中。然后我们的老朋友GC在调用析构函数的时候调用来处理队列。一个空的析构函数意味着一切都是徒劳的。请记住,正如我们已经提到的,GC执行在性能方面并不便宜。不要导致GC不必要地工作。8.避免不必要的装箱和拆箱装箱和拆箱就像垃圾回收,在性能方面很昂贵。因此,我们要避免不必要地这样做。但他们在实践中做了什么?装箱就像创建一个引用类型的盒子,然后将一个值类型的值放入其中。换句话说,它包括将值类型转换为“对象”或由该值类型实现的接口类型。相反,取消装箱,取消装箱并从中提取值类型。为什么这是个问题?好吧,正如我们已经提到的,装箱和拆箱本身就是一个昂贵的过程。除此之外,当你装箱一个值时,你会在堆上创建另一个对象,这会给GC带来额外的压力(你猜对了!)。那么,如何避免装箱和拆箱呢?通常,您可以通过避免.NET(1.0版)中早于泛型的API来实现这一点,因此必须依赖于使用对象类型。例如,比System.Collections.ArrayList更喜欢System.Collections.Generic.List等通用集合。9.小心C#/.中的字符串连接。NET,字符串是不可变的。所以每次他们做一些看起来像是在改变字符串的事情时,他们都会创建一个新字符串。这些操作包括类似Replace和Substring的方法,它们也会连接。当心连接大量字符串,尤其是在循环内部因此,这里的技巧很简单-注意不要连接大量字符串,尤其是在循环内部。在这种情况下,使用System.Text.StringBuilder类而不是“+”运算符。这确保不会为连接的每个部分创建新实例。10.密切关注C#是如何演变的最后,我们以一个非常普遍的建议结束——密切关注C#语言是如何变化和演变的。C#团队不断提供可以对性能产生积极影响的新功能。我们最近可以提到的一个例子是C#7中引入的ref[5]return和reflocals[6]。这些新功能允许开发人员通过引用返回并将引用存储在局部变量中。C#7.2引入了Span[7]类型,它支持对连续内存区域进行类型安全访问。大多数C#开发人员不太可能使用这些新功能和类型,但它们肯定会对性能关键型应用程序产生影响,值得了解更多。C#性能很重要!这只是我发现对提高我的.NET代码的性能有用的一些东西的集合-但值得花时间检查您的代码以确保其执行。您的团队和客户将感谢您!参考文献[1]Raygun:https://raygun.com/[2]JetBrains:https://www.jetbrains.com/[3]RedGateAnalyzer:http://www.red-gate.com/products/dotnet-development/ants-performance-profiler/[4]避免对GC施加太大压力的许多有用技术:https://michaelscodingspot.com/avoid-gc-pressure/[5]参考:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/ref-returns[6]和reflocals:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/ref-returns[7]跨度:https://docs.microsoft.com/en-us/dotnet/api/system.span-1?view=netcore-3.0