C++的机能优化实践

    添加时间:2013-6-5 点击量:

    优化准则:


    1. 二八法例:在任何一组器材中,最首要的只占此中一小项目组,约20%,其余80%的尽管是多半,倒是次要的;在优化实践中,我们将精力集中在优化那20%最耗时的代码上,整体机能将有明显的提拔;这个很好懂得。函数A固然代码量大,但在一次正常履行流程中,只调用了一次。而另一个函数B代码量比A小很多,但被调用了1000次。显然,我们更应存眷B的优化。

    2. 编完代码,再优化;编码的时辰老是推敲机能未必老是好的;在夸大机能的编码体式格式的同时,可能就丧失了代码的可读性和开辟效力;



    对象:



    1 Gprof



    工欲善其事,必先利其器。对于Linux平台下C++的优化,我们应用gprof对象。gprof是GNU profile对象,可以运行于linux、AIX、Sun等操纵体系进行C、C++、Pascal、Fortran法度的机能解析,用于法度的机能优化以及法度瓶颈题目的查找和解决。经由过程解析应用法度运行时产生的“flat profile”,可以获得每个函数的调用次数,消费的CPU时候(只统计CPU时候,对IO瓶颈力所不及),也可以获得函数的“调用关系图”,包含函数调用的层次关系,每个函数调用花费了几许时候。



    2. gprof应用步调



    1) 用gcc、g++、xlC编译法度时,应用-pg参数,如:g++ -pg -o test.exe test.cpp编译器会主动在目标代码中插入用于机能测试的代码片段,这些代码在法度运行时采集并记录函数的调用关系和调用次数,并记录函数自身履行时候和被调用函数的履行时候。

    2) 履行编译后的可履行法度,如:./test.exe。该步调运行法度的时候会稍慢于正常编译的可履行法度的运行时候。法度运行停止后,会在法度地点路径下生成一个缺省文件名为gmon.out的文件,这个文件就是记录法度运行的机能、调用关系、调用次数等信息的数据文件。

    3) 应用gprof号令来解析记录法度运行信息的gmon.out文件,如:gprof test.exe gmon.out则可以在显示器上看到函数调用相干的统计、解析信息。上述信息也可以采取gprof test.exe gmon.out> gprofresult.txt重定向到文本文件以便于后续解析。



    以上只是gpro的应用步调简介,关于gprof应用实例详见附录1;



    实践



    我们的法度碰到了机能瓶颈,在采取架构,改用内存数据库之前,我们推敲从代码级入手,先测验测验代码级的优化;经由过程应用gprof解析,我们发明以下2个凸起的题目:



    1.初始化大对象耗时



    解析呈报:307 6.5% VOBJ1::VOBJ1@240038VOBJ1

    在全部履行流程中被调用307次,其对象初始化耗时占到6.5%。


    这个对象很大,包含的属性多,属于根蒂根基数据布局;

    在法度进入机关函数函数体之前,类的父类对象和所有子成员变量对象已经被生成和机关。若是在机关函数体内位其履行赋值操纵,显示属于浪费。若是在机关函数时已经知道如何为类的子成员变量初始化,那么应当将这些初始化信息经由过程机关函数的初始化列表付与子成员变量,而不是在机关函数函数体中进行这些初始化。因为进入机关函数函数体之前,这些子成员变量已经初始化过一次了。


    在C++法度中,创建/烧毁对象是影响机能的一个很是凸起的操纵。起首,若是是从全局堆中生成对象,则须要起首进步履态内存分派操纵。众所周知,动态分派/收受接管在C/C++法度中一向都是很是耗时的。因为牵扯到寻找匹配大小的内存块,找到后可能还须要截断处理惩罚,然后还须要批改保护全局堆内存应用景象信息的链表等。


    解决办法:我们将大项目组的初始化操纵都移到初始化列表中,机能消费降到1.8%。



    2.Map应用不当



    解析呈报:89 6.8% Recordset::GetField

    Recordset的getField被调用了89次,机能消费占到6.8%;

    Recordset是我们在在数据库层面的包装,对应取出数据的记录集;(用过ADO的伴侣很熟悉);因为我们应用的是底层c++数据库接口,经由过程对数据库原始api进行一层包装,从而樊篱开辟人员对底层api的直接操纵。如许的包装,带来的益处就是不消直接与底层数据库交互,在代码编写方面便利不少,代码可读性也很好;带来的题目就是机能的丧失;



    解析:(2点原因)

    1)在GetField函数中,应用了map[“a”]来查询数据,若是找不到“a”,则map会主动插入key ”a”,并设value为0;而m.find(“a”)不会主动插入上述pair,履行效力更高;原有逻辑:


        string Recordset::GetField(const string &strName)
    
    {
    int nIndex;
    if (hasIndex==false)
    {
    nIndex = m_nPos;
    }
    else
    {
    nIndex = m_vSort[m_nPos].m_iorder;
    }
    if (m_fields[strName]==0)
    {
    LOG_ERR(Recordset::GetField:<<strName<< Not Find!!);
    }
    return m_records[nIndex].GetValue(m_fields[strName] - 1) ;
    }

    后的逻辑:


        string Recordset::GetField(const string &strName)
    
    {
    unordered_map::iterator iter = m_fields.find(strName);
    if (iter == m_fields.end())
    {
    LOG_ERR([Recordset::GetField] << strName <second - 1) ;
    }

    调剂后的Recordset::GetField的履行时候约是之前的1/2;且易读性更高;



    2)在Recordset中,对于每个字段的存储,应用的是map m_fields; g++中的stl标准库中默认应用的红黑树作为map的底层数据布局;

    经由过程附录中的文档2,我们发明其实有更快的布局, 在效力上,unorder map优于hash map, hash map 优于 红黑树;若是不请求map有序,unordered_map 是更好的选择;

    解决办法:将map布局换成unordered_map,机能消费降到1.4%;



    总结



    我们批改不到30行代码,整体机能提拔10%阁下,结果明显;打蛇打七寸,机能优化的关键在于找准待优化的点,之后的事,也就水到渠成;






    附录:



    附1:prof对象介绍及实践

    附2: map hash_map unordered_map 机能测试





    若是您认为这篇博客让您有些收成,请点击右下角的【推荐】按钮。


    Posted by: 大CC | 05JUN,2013



    博客:blog.me115.com



    微博:新浪微博

    真正的心灵世界会告诉你根本看不见的东西,这东西需要你付出思想和灵魂的劳动去获取,然后它会照亮你的生命,永远照亮你的生命。——王安忆《小说家的十三堂课》
    分享到: