C#面试时被人问你是如何优化你的代码的,该从哪些方面进行回答?
Posted DotNET技术圈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#面试时被人问你是如何优化你的代码的,该从哪些方面进行回答?相关的知识,希望对你有一定的参考价值。
应用层面的优化变化太多,比较难总结出系统的方法。各种通用知识更像一本字典,实际中需要根据情景选择使用。与其后手优化,最好能先手写出高性能的代码。在此简单整理一下重点。
基本原则
首先说一下比较上层的原则:
1.最重要的是准确测量。2.相较于优化代码,更重要的是对程序性能持续不断的管理。3.深度优化往往意味着牺牲代码抽象。
测量
随便看几本优化方面的书,基本都会从测量开始讲。测量的难度很容易被低估,思路是解决两个问题:1. 测什么。2. 用什么测。
“测什么”考验你对应用的性能模型是否有很好的理解。比如优化C#的内存性能,需要测什么?峰值大小,还是平均工作集大小?1代对象大小还是2代对象大小,还是LOH大小?需不需要测Page Fault?这些问题根据应用的不同答案都是不同的,能根据情景定位到正确的测量目标是很重要的能力。
用什么测
“用什么测”是工具的问题。Profiler是最简单的,但是离开了debug环境很有可能就不适用了。比如某个问题只能在部署环境下重现,如何测量?这就需要对各种工具有一定了解,比如利用Performance Counter或ETW+PrefView/CLR Profiler来定位瓶颈,用WinDbg具体分析内存问题和并发性能问题等等。微软的工具有比较完善的手册和教程来系统地学习,不过学习曲线也是比较陡的。
考虑修复成本
软件开发中缺陷发现的越早,修复的成本也越低,这个定律同样适合于性能上的缺陷。持续的性能管理可以明显减轻优化的负担,很多低效的设计在大规模使用之前就能得到修正。这个更多是团队文化的问题,常常也是身不由己,不展开说了。
深度优化
深度优化与代码抽像之间的矛盾也是显而易见的。例如虚函数,虚函数会使JIT内联优化失效,但是否采用虚函数设计也要看具体情况。如果是内部代码,性能也非常敏感,可以考虑放弃虚函数。但是供第三方使用的类库,对扩展性和易用性的需求可能就更重要。而且这种深度优化,在其他部分已经没什么油水可榨的时候才需要考虑,实际上大部分程序都不用轮到牺牲抽象就可以表现的足够好。
C#代码优化的主要知识点
抛开通用的算法优化,具体到C#优化方面的知识,重点有几大块:
•对GC的优化。•对JIT的优化。•对并发的优化。•对.NET框架的理解。•语言机制上的优化。•跨平台优化。
下面简单展开。
GC
GC和JIT是C#/.NET的一个很重要的特征,也是性能问题频繁发生的地方。GC和JIT属于Runtime层面的服务,代码上并不能直接控制,所以优化的思路集中在理解和适应上,扬长避短是重点。对于GC,要理解CLR的GC算法,算法本身理解起来并不困难,主要关注几个方面:
1.0、1、2代对象和LOH是什么。2.GC发生的时机和大致过程。3.GC挂起的时间跟哪些因素有关。4.Pinning和析构函数对GC的负面作用。
分代回收
比较深入的还有NoGCRegion,GC Notification等等。其实总结出来就是要管好对象的生命周期,要么速度滚到2代一辈子不被回收,要么在0代就立刻被回收掉,析构之类的其他机制仅在绝对必要时小规模使用。对象池一类的模式也都是基于这个原则。
JIT优化
JIT优化主要两方面:启动时间和生成代码的优化。程序启动时会JIT编译大量代码,是.NET程序启动慢的普遍原因,优化需要精简启动代码,也可以利用NGEN,MPGO,ProfileOptimization一类工具。JIT在生成代码时最重要的优化是代码内联,很多写法会让内联优化失效,如果希望函数或属性内联,应避免:递归调用,IL代码超过32B,函数包含循环和异常处理,虚函数,接口类型在同一个call site可能有多种实现(这个最伤,不只没法内联),等等。
并发
另一大块是并发,这块很多语言无关的通用原则都适用。C#的重点是:
1.理解托管线程模型,Thread,ThreadPool等。2.会用TPL,性能方面要能够根据情况指定正确的creation options和continuation options。3.能避免阻塞线程,比如避免Task.Wait,避免调用阻塞IO。4.正确看待同步,加锁永远不会提高程序性能,最快的同步是没有同步。要写的话,Interlocked要会用,要明白Slim版的锁为什么好之类的。
理解.NET 框架
编程上的优化
编码上的优化。这个细节太多了,最最基础的、一定要掌握的是引用类型和值类型的区别,并且能懂得避免值类型boxing和避免分配不必要的引用类型实例。这个随便一搜资料就一大把,是C#基础中的基础。再列举几个细节:
•struct默认的Equals和GetHashCode是用反射实现的,如果不是blittable type,性能非常差,要override。•用explicit structure layout可以绕过反射从struct里偷private变量出来,非常快。•interface dynamic dispatch问题。之前说的同一个call site如果实现类型不一样可能会变慢的问题。最近发现Swift也有一样的问题,也许是多重继承的通病。具体参考:Digging into interface calls in the .NET Framework: Stub-based dispatch.[2]•不得不反射调用代码的时候可以考虑用IL动态生成一个强类型委托,频繁调用的开销基本能降到和普通方法同等水平。
类似这种小技巧非常非常多,当然很多都是以牺牲代码抽象为代价的。深度优化一定要确定值得再去做,判断依据还是测量数据。
跨平台优化
跨平台优化主要是考虑到C#如今已经可以运行在很多平台上,Windows,ios,android,Unity,WP,这些平台的C#实现或多或少有一些区别,对各种平台Runtime的实现有一定了解,才能有效地把这些优化技巧从一个平台迁移到另一个平台,写客户端的人可能感受比较深。
总结
C#优化方面的知识大概都是在这个框架之下的,最直接的获取方式还是平时多观察多解决问题。性能优化虽然有一些通用的原则,然而实际运用中能够落实到具体的语言和框架上才真正有用。
References
[1]
Reference Source: https://link.zhihu.com/?target=http%3A//referencesource.microsoft.com/[2]
Digging into interface calls in the .NET Framework: Stub-based dispatch.: https://link.zhihu.com/?target=http%3A//blogs.msdn.com/b/vancem/archive/2006/03/13/550529.aspx
以上是关于C#面试时被人问你是如何优化你的代码的,该从哪些方面进行回答?的主要内容,如果未能解决你的问题,请参考以下文章
大厂面试官问你知道finalfinallyfinalize有什么区别?