找回密码
 立即注册

QQ登录

只需一步,快速开始

工控课堂 首页 工控文库 上位机编程 查看内容

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

2022-2-20 15:07| 发布者: gk-auto| 查看: 530| 评论: 10|原作者: gk-auto

摘要: C#性能提示和技巧在Raygun,我们是一群非常懂多种语言的开发人员。Raygun的各个部分使用不同的语言和框架编写-最好的工作方式。鉴于大量的C#和我们正在处理的数据的爆炸性增长,在不同的时间需要进行一些优化工作 ...

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。我们要计算有多少个插槽的值大于零。
问题–这两个中哪个更快?
for (int i = 0; i < _map.Length; i++){    for (int n = 0; n < _map.Length; n++)    {          if (_map[n] > 0)          {            result++;          }    }}for (int i = 0; i < _map.Length; i++){    for (int n = 0; n < _map.Length; n++)    {          if (_map[n] > 0)          {            result++;          }    }}
回答?第一个。在我的测试中,此循环使性能提高了8倍!
注意区别吗?这是我们遍历此数组数组的顺序( [n]与[n] )。即使我们从自己管理内存中抽象出来,内存局部性在.NET中的确很重要。
就我而言,这种方法被称为数百万次(准确地说是数亿次),因此我可以从中获得的任何性能都获得了可观的胜利。再次感谢我经常使用的分析器,以确保我专注于正确的地方!
6.减轻垃圾收集器的压力
C#/.NET具有垃圾回收功能。垃圾收集是确定哪些对象当前已过时并删除它们以释放内存中空间的过程。这意味着在C#中,与C ++之类的语言不同,您不必手动维护不再有用的对象的删除,即可声明其在内存中的空间。相反,垃圾收集器(GC)处理所有这些,因此您不必这样做。
问题是没有免费的午餐
问题是没有免费的午餐。收集过程本身会导致性能下降,因此您实际上并不希望GC一直收集。那么如何避免这种情况呢?
有许多有用的技术可以避免对GC施加太大压力[4]。在这里,我将只关注一个技巧:避免不必要的分配。这意味着要避免这样的事情:
List<Product> products = new List<Product>();products = productRepo.All();
第一行创建了一个完全无用的列表实例,因为下一行返回另一个实例并将其引用分配给变量。现在想象一下上面的两行是否在一个执行数千次的循环中?
上面的代码可能看起来像一个愚蠢的示例,但是我已经在生产中看到了这样的代码,而不仅仅是一次。不要只关注示例本身,而要关注一般建议。除非确实需要,否则不要创建对象。
由于GC在.NET中的工作方式(这是一个世代的GC流程),因此较旧的对象更有可能收集较新的对象。这意味着创建许多新的,短暂的对象可能会触发GC运行。
7.不要使用空的析构函数
标题说明了一切-请勿在类中添加空的析构函数。Finalize每个具有析构函数的类的条目都会添加到队列中。然后在调用析构函数时调用我们的老朋友GC来处理队列。空的析构函数意味着这一切都是徒劳的。


关注公众号,加入500人微信群,下载100G免费资料!
发表评论

最新评论

引用 Que.十六 2025-11-13 15:19
我先占个楼,等下再慢慢看~
引用 易卖工控 2025-11-13 15:26
楼主太会说了,字字句句都在理
引用 米斯特王 2025-11-13 15:33
同款经历!我当初也这么过来的😂
引用 yy240084 2025-11-13 16:08
学到干货了,感谢分享,已火速收藏
引用 panenmei320 2025-11-13 16:18
楼主辛苦啦,期待下一篇分享!
引用 飞翔自由 2025-11-13 16:31
理性围观,感觉大家说得都有道理
引用 张宇峰 2025-11-15 20:26
路过混个脸熟,顺便为优质内容打 call~
引用 924685652 2025-11-16 07:36
同款经历!简直是世另我
引用 rubiao 2025-11-17 02:11
这波分析到位,逻辑满分!
引用 姚知建 2025-11-17 06:38
内容太顶了!疯狂点赞,已默默收藏~

查看全部评论(10)

热门文章
关闭

站长推荐上一条 /1 下一条

QQ|手机版|免责声明|本站介绍|工控课堂 ( 沪ICP备20008691号-1 )

GMT+8, 2025-12-22 23:44 , Processed in 0.500417 second(s), 28 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

返回顶部