打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
高性能程序设计经验总结

最近读了一些书,面试了一些程序员,也分析和设计编写了一些功能组件,其中有很多和高性能有关的东西,有必要归纳从中获取的经验,整理总结,引以复用。

我把优化分为三个领域,如下:

1、问题领域

指的是在设计阶段建立问题模型时,所使用的优化方案,在这个领域上,由系统的架构师来负责。

1)问题的解决方案在宏观上是否可以并行化。

随着对称多处理(SMP)技术的日益发展,并行加速已经走出实验室和大型企业,深入影响到每个人的生活中。简单的举个例子,我们在收听天气预报,乘坐飞机甚至玩电子游戏的时候都在间接或者直接的享受并行计算带来的好处。那么作为IT从业人员的我们也应该在本职工作中利用这种技术给产品带来更快的速度和更好的体验。

若一个问题可以分成多个相同或相似的部分,并且可以把这些部分交给不同的执行者一起执行从而缩短问题解决的时间,我们认为该问题是可以并行的。定义看起来比较拗口,不如举个生活中的例子,给一块田插秧是可以并行的,但生一个孩子则不同。从含义和例子上我们可以看出,子问题如果时间上有依赖关系,就不能并行,反之则可以。

这就意味着,如果我们想要使用这种技术,就要我们在设计问题解决模型时理清那些子问题的依赖,把没有相互依赖的重要部分整理出来,拆分给不同的执行者去执行。我们可以使用多线程、多进程甚至多机处理的方式来实现并行加速。目前IT业界火热的hadoop技术使用的是多机并行,而我们公司的iAMS产品在设计上就是用了多线程方式加速,同时也支持多进程/多机并行加速和热备,这对我们产品性能和稳定性提升带来很大的帮助。

此处说的宏观是问题领域上的,和计算机结构上的微观对应(见3.1)

2)选择好适当的数据结构和算法。

数据结构是图纸,算法则是操作手册,两者分别是程序的静剖面和动态的路线图。良好的数据结构可以使得程序构造起来更为方便,而好的执行方法则可以提升效率和减少成本。所以在解决问题时要同时考虑两者,不可偏废,由于数据结构可读性较强,我一般选择从其开始,在之上再选择合适的时间复杂度低以及避免出现最坏情况的算法。

我们在处理一种队列问题时,如果仅仅在一头插入删除,那么顺序表即可(如std::vector);如果要求头尾插入删除操作,我们选用系数稍高,但是时间复杂度不变的链表则可以很方便的解决问题(如std::list); 若又要求队列有序,比起用线性表每次都要重新排序,我们用平衡树效率更高(如std::map)。

又比如我们在程序中大量遇到的查找/排序问题,很多时候没有对数据结构进行选择,构造好存储之后,每次业务来了都有要遍历查找,这时时间复杂度一般是O(n)。如果存储之后先排序,再查找,这样查询的性能就会得到很大提升。我们可以使用普通vector存储,然后使用排序,最后使用二分查找,可以把查询的时间复杂度降到O(logn);又或我们用map存储,内部已然有序,查找的时间复杂度也是O(n)。至于是使用vector还是map,可根据其他业务需要,例如是否需要随机访问,是否会在查询业务同时也会有数据的增删等特点来选择。

至于排序,一般情况下我们会用快排来替代冒泡,选择等这种时间复杂度更高的算法,但对于特定问题,例如基本有序的,或者排序个数很少,但是排序次数很多的,我们还会选择更合适的选择排序。而C++ STL中的std::sort也考虑到通用性,内部实现一般使用快排和选择混合排序来普适排序问题。

2、编码领域

所有问题共性之下,而又抽象于计算机结构之上的普适的编码优化。

我们通常认为问题领域之下就是计算机结构领域,但是在实践的时候不会分的如此绝对。因为如果问题领域涉及的过于细节,则不利于全局问题的把握,也不利于整个问题的解决。所以往往有一中间层,一般由对应组件的开发工程师负责。

1)选择和设计性能较好的基础库

对于架构师来说,选择什么样的线程池,或者用哪种xml解析器来实现一项子功能是喧宾夺主,而对于开发工程师来说则是分内之事。有些工具例如线程池、连接池、IO多路复用库等的选择和实现是他们必须要认真考虑的。这些工具都是某些共性问题的快速解决方案,不但比普通解决方法性能更好,节省开发时间提高执行效率,还能给后期维护带来便利。

我们在产品开发过程中会积累很多基础功能代码,这些基础代码若能良好封装成自有的基础开发库,无论是对本产品还是新产品都有着重要的作用。一旦架构师确定了我们在开发中要使用某个技术,工程师就面临着选择:我应该手写代码实现呢,还是写一个基础工具库拿来给所有人用,或者直接用别人写好的呢?对于这个问题我强烈不建议为了偷懒就写个当前功能能用的代码,而是推荐为维护起自己产品线的基础库和使用稳定成熟的第三方库。这两者并不矛盾,iAMS产品线既使用了第三方库如tinyxml和libevent,又有自己设计实现跨平台的的线程池、同步异步、IO操作等工具集。在多线程任务频繁更换调度的情况下,使用线程池明显比使用手写线程性能高很多。即使不是需要高性能的多线程任务,复用线程池也能大幅减少开发和维护的时间。

2)尽量消除实现时的重复/无关计算

重复计算在生活中是个常见问题。例如上次我让测试人员帮我统计了某个功能的性能,没有记录,后来需要的时候自己又重新测试的一遍。在计算机软件上也更是普遍,开发工程师在实现某项功能的时候往往会有设计不够清晰,实现过程思路被打断,组件复用桥接等问题。这些问题会导致例子中的类似效果,某些问题解决了没有记录或不知晓,重复解决很多次;有些问题处理过程和待解决的问题毫无关系,时间白白浪费。单个功能开销可能不大,但产品由很多功能组成,如果每个功能都存在很多时间浪费,整个产品的总开销就很可观了。对于这两类问题,我们也有一些解决方法:

充分理解功能实现逻辑和所用基本组件,实现功能清晰明确;

设计功能单一的基础模块,模块再组合成特定功能集合的大模块,而不是只一个大模块导致复用时的计算浪费。

3、计算机结构领域

需要结合计算机特性的优化方针,开发工程师非常了解计算机硬件系统特性才能做出的优化,一般将其归入该领域。

1)充分利用处理器多发射超标量等内部并行性(如展开循环);

2)提高代码的局部性(多使用局部变量代替存储器引用,内存尽量顺序访问-能用vector就不用list,等等;

3)防止寄存器溢出(不要过多的局部变量,但X64比X86有着更多的寄存器);

4)防止打断CPU流水线(减少不可预测的分支判断,使用条件转移指令者替代或者以小的代价消除分支,考虑移除循环尤其是多级循环内的分支);

5)使用扩展指令集来提高性能(如MMX,SSE,AVX等)。

以上三个领域,对于一个软件优化来说考虑顺序和费效比都是由高到低,所费篇幅也如此,性能提升效率大概分别为:几何级、数量级、倍数级。我们应该尽量在前两个,尤其是第一个领域就把性能达到预定的水平。因为越往下,花费的时间和精力越多,而得到的好处只是越少的。而且这些好处并不是凭空产出的,就像炼金术师说的那样:“人没有牺牲就什么都得不到,为了得到什么东西,就需要付出同等的代价”

所以我们最后一个话题就是:代价

在实际开发中,需要根据具体问题来选择优化方案。很多优化措施都会带来一些附加成本或条件,选择合适的能在较小的代价下获取较大的提升。

方案1.1必须在多核/多处理机上才能发挥作用,而且会提高设计方案的复杂性以及开发/维护上的成本。

方案1.2性能更高的红黑树需要比AVL树更复杂的代码,这样就带来开发和维护上的成本增加。

方案3.1和3.4可能会降低代码的可读性,从而导致维护成本升高。

方案3.5则会降低程序的通用性。

由于优化是有代价的,所以我们在优化前要确定这程序/模块是否需要优化。D.E.Knuth说:过早优化是万恶之源,没有仔细分析的优化无异于谋杀进度。在设计开发程序之前,要根据针对设计热点提前设计上和实现上的改进。若是优化已有程序则需要用prof工具检测到热点再对其改造。至于如何检测程序热点,那就不是本文所涉及的领域了。

欢迎点赞或收藏。如有大数据爱好者,欢迎共同探讨。

关于IDEADATA:IDEADATA专注于从数据到信息的有效管理与应用,是领先的商业信息服务技术提供商,是数据仓库及大数据技术和应用的先行实践者。

微信关注:IDEADATA大数据视界

新浪微博:iDEADATA大数据视界公司

官网:www.ideadata.com.cn

作者:头条号 / IDEADATA大数据

链接:http://www.ideadata.com.cn/wisdomAction/readWisdom.do?id=73

来源:头条号(今日头条旗下创作平台)

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
一文揭秘DDD到底解决了什么问题
修改注册表来优化Win10对CPU超线程技术的调度问题(2)
教你修改注册表来优化Win10对CPU超线程技术的调度问题
cuda高性能并发编程入门
8核16线程的酷睿H35升级版?还是H45要来了!
百度2012实习生校园招聘笔试题
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服