zoukankan      html  css  js  c++  java
  • C++项目性能调优——基于VC6

    总结一下最近一段时间做性能优化的经验。

    项目介绍

    这是手头上正在做的一个产品,以C++和MFC为主要开发工具和环境。产品代码量至少60W行以上。目前这个产品在性能上和竞争对手的差距在逐渐增大,究其原因在于:

    1. 产品陈旧,缺少必要的重构。该产品已经问世10年,在整个开发过程中,积累巨量的技术债并且因为各方面的原因没有进行过有计划的阶段性重构
    2. 在架构上没有多线程的影子,I/O也是同步模式
    3. 在如此长的生命周期中,需求已经发生了巨大的变化,很多老旧无用的代码没有及时剔除

    性能调优的方向

    • 运行时间的优化
      • 启动时间
      • 对常用操作的响应时间
    • 内存占用的优化
      • 内存泄漏
      • 长时间运行时内存占用量

    目前的进展情况

    启动时间缩减了大概500ms(测试机上,原先3s左右,现在2点多秒)。内存长期运行占用率减少了30%左右,但是这个结果是作弊而来的。。。

    如何进行性能调优

    从目前的总结的经验来看,我个人觉得方法论上和很多人说的都没啥区别,无非是:

    1. 找瓶颈。
    2. 定方案。

    基本的策略

    分析瓶颈所在

    这里着重讨论运行时间上的优化。

    在运行时的优化上,要考虑两个维度:

    1. 耗时较多的函数
    2. 扇入多或者被频繁调用的函数

    因此我们需要有方法或者工具来确定程序运行中有哪些函数达到了我们既定的标准。要获得函数运行的时间我们可以在函数中插入一些计时器,典型的就是使用QueryPerformanceCounterQueryPerformanceFrequency。在这一点上切记不可以使用GetTickCount或者GetTickCount64,主要原因就是这两个函数的精度太低了,在10-16毫秒之间。而获得函数的调用次数,就没有获取时间这么来得方便了。因此要解决这两个问题还是用现有的工具的来得比较方便和有效。

    使用恰当的工具

    在Windows平台上至少有两个工具可以使用。一个就是大名鼎鼎的Intel VTune。还有就是Visual Studio自带的PROFILE(VS6),在后续的VS版本里还有更好的工具,像VSPerf(还不是很会用)。

    这里不打算介绍VTune。主要原因是这货实在太笨重,而且如果机器性能不怎么样,你要跑这个工具都很坑爹。VS6自带的Profile相对来说就比较轻量级,并且完全满足我们的需求。

    Profile工具主要由3个小工具配合使用,一是PREP;一是PROFILE;另外就是PLIST。在使用上可以通过命令行也可以通过IDE来。这里就说一说如何用IDE。Ide用起来方便么J

    首先要配置工程属性启用profile。然后重新编译链接程序。细节可以参考这里

    收集性能数据可以针对编译单元也可以针对指定的函数。点击菜单Build/Profile…会弹出一个对话框。如果要针对编译单元可以在Advanced Setting:里写上"/EXCALL /INC targetObjFile.obj"。如果要针对某个函数可以填"/SF CppDecoratedFuncName"。函数名称必须是能在对应的map文件里找的到的函数。具体的使用方法可以参考这里这里这里

    关于profile的完整参考请猛击这里

    解决方案

    现在有了性能数据,接下来就是着手解决了。事实上性能调优的解决方案也就那么几种:

    1. 空间换时间
    2. 并发执行
    3. 移除老旧无用代码和精简逻辑

    空间换时间是比较容易做,但是不太容易做好的。这个方案在最早的一次性能调优时就已经应用起来了。当时没有记录下性能提升的数据,因此现在也很难说到底有多有效。说不容易做好主要还是很多时候都轻易的下结论使用map来存储数据而没有考虑到数据量到底有多大,是不是真的需要有序。特别是像VC6这种,只有map没有unordered_map。

    移除老旧代码等其实是最简单并且可以很快就看到好处的一个方案。特别是像我现在的这个环境,很多的判断都是无用的判断,完全可以直接抹去。在抹去的过程中有两种选择,一种是直接删除;还就是代码保留但是全部都注释掉。选取那种方案要看具体的情况了。我这里选择注释掉代码。但是考虑到有些地方是成片注释的,因此也会把这连续的一片代码包进一个函数,然后把函数调用的地方注释掉。在做启动项优化的时候,我注释掉了数千把行代码,同时将部分逻辑做了简化,这个换取了100多毫秒的提升!!!

    最后说一说并发。前面说了,手头上这个产品主干上都是单线程的。在分析一些耗时特别长但调用次数不多的函数上时,要好好考虑并发这个方案。这也要从两个方面来考虑,一是要考虑使用线程池;二是要分析代码逻辑,将并发的业务逻辑提取出来。

    线程池可以考虑使用Windows自带的线程池也可以自己写。闲着蛋疼,前段时间自己写了一个基于APC的简单线程池。解决问题的关键还是在第二点。

    将代码并发执行具体可以分下面几点来说:

    1. 同步I/O的处理。这是最难的一点。基本上可以说,I/O肯定是程序中处于比较底层的一个模块了,同步改成异步对整体结构的影响无法评估。至少在最近这个月的工作中,我还没法想象该如何实施。因此,同步I/O的处理暂时无法实施。
    2. 事务的并发处理。这个相对而言比较好入手并且可衡量。这里需要注意的是如何处理好时间的分摊。可以使用伪寄存器@clk来大致划分并发工作量。另外事务的并发执行上可以考虑使用目前浏览器在下载资源时普遍采用的解决方案,在没有点确定下载前,就可以启动下载流程了。这个方案对降低整个流程的耗时是非常显而易见的。
    3. 事务并发处理后的同步。尽可能减少同步,最好是不需要同步。如果同步不可避免,那么等待的点一定要选好,尽可能避免主线程开始等待的时候,线程还没有执行完毕。

    说到这里,基本上关于运行时的性能优化就这么多了。下面简单说一说内存方面所做的尝试。之所以把内存项的内容放到最后来说是因为这方面的进展比较缓慢L

    内存使用上的改进

    刚才说了内存上要解决两个问题,一是泄露;一是占用量。

    内存泄露的检测有很多工具,这里推荐使用Visual Leak Detector。另外,占用量方面的优化前面说了是骗人的,因为用了一个Win32的函数SetProcessWorkingSetSize

    好了,内存占用方面的我说完了。。。。。还望看官海涵。。。

    总结

    如果你有时候还在使用MFC的话,可以看看这个总结。

    1. 不是什么时候都要用map来存数据。
    2. 少用IsKindOf
    3. CString的使用要合理
    4. 其实MSDN已经有代码上的指导性原则了。。请看这里

    另外,MSDN上关于Function Timing的说明,应该是有误的。单位应该是ms不是s。

  • 相关阅读:
    线程池
    多线程随笔
    注解随笔
    反射机制
    iO流
    FastDFS+docker建立分布式文件系统
    Java之Exception
    Java之String
    手写SpringMvc
    spring中一些常用注解的含义
  • 原文地址:https://www.cnblogs.com/wpcockroach/p/3122767.html
Copyright © 2011-2022 走看看