C++应用程序性能优化读书笔记

粗浅的看了下,有点虎头蛇尾。暂且记下。

 

第一篇 C++程序优化基础

第1章 C++对象模型

1.1 基本概念

1.1.1 程序使用内存区

    一个程序占用的内存区一般分为5种:全局/静态数据区、常量区、代码区、栈、堆。

    例子代码:
    #include <stdio.h>
    #include <stdlib.h>

    int nGlobal = 100;

    int main(void)
    {
        char *pLocalString1 = "LocalString1";  // 书本有误
        const char *pLocalString2 = "LocalString2";
   
        // 如下
        char *pLocalString3 = "LocalString3";
        char **pLocalString4 = &pLocalString3;

        static int nLocalStatic = 100;

        int nLocal1 = 1;
        const int nLocalConst = 20;

        int *pNew = new int[5];
        char *pMalloc = (char*)malloc(1);

        printf("global variable:                0x%x/n", &nGlobal);
        printf("static variable:                0x%x/n", &nLocalStatic);
        printf("local expression 1:                0x%x/n", pLocalString1);
        printf("local expression (const):        0x%x/n", pLocalString2);
        printf("/n");

        printf("new:                            0x%x/n", pNew);
        printf("malloc:                            0x%x/n", pMalloc);
        printf("/n");

        printf("local poniter(pNew):            0x%x/n", &pNew);
        printf("local poniter(pLocalString2):    0x%x/n", &pLocalString2);
        printf("local poniter(pLocalString1):    0x%x/n", &pLocalString1);
        printf("local variable(nLocal):            0x%x/n", &pMalloc);
        printf("local poniter(pmalloc):            0x%x/n", &pMalloc);
        printf("local const varible:            0x%x/n", &nLocalConst);

        return 0;
    }
    常量存储区是按4个字节对齐的,堆上分配的内存是16个字节对齐。
   
    C/C++程序中的struct、union、class在编译时也会对变量进行对齐处理。可以用#pragma pack()或者编译

选项来控制struct、union、class的成员变量俺多少字节对齐,或者关闭对齐。

1.1.2 全局/静态存储区及常量数据区
   
    下面这段代码是错误的:
    char *pLocalString = "LocalString";    // 指向一个字符串常量
    pLocalString[1] = a;    // 试图修改不可修改的内存区
   
    静态变量在第一次进入作用域时被初始化。在同一个类的多个对象之间共享数据,(可以使用全局变量,但

会破坏类的封装性),我们使用静态变量。如下:
    #include <stdio.h>
    #include <stdlib.h>

    class A
    {
    public:
        int val;
        static int nCount;
        A() {nCount++;};
        A~() {nCount--;};
    }   

    int A::nCount = 0;

    int main()
    {
        A a;
        B b;
        printf("number of A:        %d/n", A::nCount);
        printf("non-static variable: 0x%x/n", &a.val);
        printf("non-static variable: 0x%x/n", &b.val);
        printf("static class member: 0x%x/n", &a.nCount);
        printf("static class member: 0x%x/n", &b.nCount);
    }

1.1.3 堆和栈
   
    栈自动释放;堆由开发人员释放。
    栈的大小是固定的,有编译器决定,vs2003的默认值为1MB;堆只受限于系统有效的虚拟内存。
    栈的效率较高不产生碎片;堆效率低,容易产生碎片。

1.1.4 C++中的对象

    对象的三种创建方式:
    1) 通过定义变量创建
    2) 通过new操作符创建
    3) 通过实现创建对象:这种情况一般是指一些隐藏的中间临时变量的创建和销毁。
                 它们的生命周期很短,也不易被开发人员发觉,但常常是造成程序性能下降的瓶颈


       
    #include <stdio.h>
    class A
    {
    public:
        A() { printf("A create./n");}
        A(A& a) { printf("A create with copy/n");}
        ~A() { printf("A destroyed. /n");}
    };

    A foo(A a)
    {
        A b;
        return b;
    }

    int main(void)
    {
        A a;
        a = foo( a);
        return 0;
    }
   
    结果如下:
    A create.
    A create with copy
    A create.
    A create with copy
    A destroyed.
    A destroyed.
    A destroyed.
    A destroyed.
    Press any key to continue   

    原因:foo()的参数和返回值都是通过值传递的。在调用foo()的时候,需要把实参复制一份,压入foo()的

栈中,返回值也要复制一份放在栈中。
    解决办法:可以通过传递引用的方式来解决,即A foo(A& a)。返回值则可以根据情况返回指针或引用,也

可以增加一个指针类型的参数作为函数的输出。
   
    在C++软件开发中:还会有大量其他类型的隐性临时对象存在,如重载+以及++等操作符。
    当一个派生类实例化一个对象时,会先构造一个父类对象。同样在销毁一个派生类的对象时,要首先销毁其

父类对象。如果构造父类对象的开销很大,会造成所有子类的构造都有很大的开销。

1.3 C++对象的内存布局

1.3.1 简单对象

    简单C++对象内存布局:
    1) 非静态数据成员是影响对象占据内存大小的主要因素(4字节对齐)
    2) 静态数据成员不占对象内存
    3) 静态成员函数和非静态成员函数不会影响内存的大小
    4) 如果对象中包含虚函数,会增加4个字节的空间,不管有多少个虚函数
       虚函数表vtale中不必完全是指向虚函数实现的指针。当指定编译器打开RTTI开关时,vtable中的第一个

指针指向的是一个typeinfo的结构,每个类只产生一个typeinfo结构的实例。当程序调用typeid()来获取类的信息时

,实际上就是通过vtable中的第一个指针获得了typeinfo。

1.3.2 单继承

 

    // 此处应该是被修改 参考这篇文章http://student.csdn.net/space.php?uid=112600&do=blog&id=6424

    派生类在构造时,不会在创建一个新的虚函数表,而应该在基类的虚函数表中增加或修改。

1.3.3 多继承
   
    多继承中派生类的内存包含了多个完整的基类内存(即每个基类都有自己的虚函数表)。
    多继承要注意避免二义性(编译器会报二义性错误)。

    菱形继承:
    在派生类中有两个中间类,而每个中间类都会有一个基类。不仅浪费了内存,而且会产生二义性。
    所以派生类在调用时必需指明调用的是哪一个基类的方法:
    aSimple.midClass2::SetValue();
    aSimple.baseClass::GetValue();    //此处出现问题,编译器默认调用的是

aSimple.midClass1::baseClass::GetValue()
   
    aSimple.midClass1::SetValue();
    aSimple.baseClass::GetValue();
   
    解决办法:
    虚拟继承:当使用虚拟继承时,公共的基类只存在一个实例。
    class midClass1 : virtual public baseClass
    ...
    class midClass2 : virtual public baseClass
    ...
    baseClass的实例放在derivedClass实例的内存空间的最后一部分
    为了支持虚拟继承,不同的编译器的做法会有所不同
    vc++中:
    不使用虚拟继承时    使用虚拟继承时
    baseClass 12字节    baseClass 12字节
    midClass1 16字节    midClass1 24字节    (+8 增加了虚基类表、虚函数表?)   
    midClass2 16字节    midClass2 24字节
    derivedClass  36    derivedClass  40    ( 8 + 8 - 12?)

1.4 构造与析构
   
    编译器会提够默认的构造函数,此函数不带任何参数,也不会对成员数据进行任何初始化。
    必需自己定义,建议使用构造函数初始化列表(C++Primer)。
    编译器会提供默认的拷贝构造函数,这个函数执行的是位拷贝,在有new存在的时候会出现问题。
    必需自己定义拷贝构造函数。
   


第2章 C++语言特性的性能分析

    当一个程序的性能需要提高时,首先要做的就是用性能检测工具对其运行时间的分布进行一个准确的测量,

找出关键路径和瓶颈所在,然后针对瓶颈进行分析和优化。   
    C++中的一个语言特性比其他因素更容易成为程序的瓶颈:
    1) 缺页(第4章) 缺页意味着需要访问外部内存。应尽量想办法减少缺页。
    2) 从堆中动态申请和释放内存 优先考虑栈而减少从动态堆中申请内存。因为在堆中开辟内存比在栈中要慢

很多,而且还会引起缺页
    3) 复杂对象的创建和销毁  非常注意
    4)  函数调用 C语言的宏和C++的内联函数都是为了保持函数调用的模块化特征基础上消除函数调用的固定

额外开销而引入的。宏提供性能优势的同事也给开发和调试带来了不便,在C++中更多提倡的是使用内联函数。

2.1 构造函数与析构函数
   
    创建一个对象分成两个步骤:首先取得对象所需的内存,然后在该块内存上执行构造函数。构造函数也分两

个步骤:先执行初始化(通过初始化列表),再执行构造函数的函数体。
    构造函数是一个递归操作,在每层递归内部的操作遵循严格的次序。
    构造该类自己的成员变量时, 1)严格按照成员变量在类中的声明顺序进行,而与其在初始化列表中出现的

顺序无关
                   2)当有些成员变量或父类对象没有在初始化列表中出现时,它们仍然在初始

化操作这一步骤中被初始化
    常量( const)型和引用(refence)型必须在初始化列表中正确初始化,而不能将其初始化放在构造函数内。
   
    析构函数与构造函数逆序。

    开发人员要尽量避免编译器为其程序生成的临时对象。

    减少对象创建/销毁的一个很简单且常见的方法是在函数声明中降所有的值传递改为常量引用传递。

2.2 继承与虚函数

    除非通过性能检测确定性能的瓶颈是由于虚拟函数没有利用到内联函数的优势这一缺陷引起,否则可以不必

考虑虚拟函数的影响。

2.3 临时对象

    产生临时对象一般有如下两种场合:
    1) 当实际调用函数时传入的参数与函数定义中声明的变量类型不匹配
    2) 当函数返回一个对象时(有例外)
   
    class Rational
    {
    piblic:   
        /*explicit*/ Rational( int a=0, int b=1):m(a),n(b){}
    pravite:
        int m, n;
    }
    ...
   
    void foo()
    {
        Rational r;
        r = 100;    // 看起来无法编译,但是编译器会将右边的100通过调用Rational::Rational

(100,1)生成一个临时对象
        ...
    }
   
    通过对类的构造函数添加"explicit"来阻止编译器进行自动类型转换,即阻止因此引起的临时对象的产生。
    explicit的含义是开发人员只能根据这个构造函数的定义调用,而不允许编译器利用其来进行隐式的类型转

换。
   
    非内建类型的对象,尽量将对象延迟到已经确切知道其有效状态时。
    Rational r;    // 应写为 Rational r = a+b, 此时是一个初始化,而不是赋值,不会在重载+的函数返

回值的时候创建临时对象
    r = a+b;

    const Rational operate+( const Rational& a, const Rational& b)
    {
        Rational temp;
        temp.m = a.m + b.m;
        temp.n = a.n + b.n;
        return temp;
    }
    可优化为
    const Ratioanl operate+( const Rational& a, const Rational& b)
    {
        return Rational( a.m + b.m, a.n + b.n);         // 与上一个优化配合才有效
    }

    在设计类时,如果要重载operator+,最好也重载operator+=,并且考虑到维护性,operator+用operator+=来

实现,只须更改一处。
    Rational& operator+=( const Rational& rhs)            // 特别注意此处的返回值类型为

Rational&
    {
        m += rhs.m;
        n += rhs.n;
        return (*this)
    }
    const Ratioanl operate+( const Rational& a, const Rational& b)
    {
        return Rational(a) += b;        
    }   
    同理 -=, *=, /=。

    尽量使用++i, --i; 因为前置是先将值返回,然后其值增1;后置是先讲值增1,再返回其值,实现中必须

要保留其原来的值。

    string a, b;
    const char* str;
    if( strlen( str = (a+b).c_str() ) > 5)        //1   
    {
        printf("%s/n",str);            //2
        ...
    }
    存放a+b临时的临时对象的生命在包含其创建的最长语句结束后也相应的结束了,1处。当执行到2处,该临

时对象已经不存在。指向它内部字符串内容的str指向的是一段已经被回收的内存。会出现问题。
    但这条规范有一个特例,当用一个临时对象来初始化一个常量引用时,该临时对象的生命会持续到与其绑定

到其上的常量引用销毁时
    string a,b;
    if(...)
    {
        const string& c = a + b;    // 临时对象被绑定
        cout << c << endl;
        ...;
    }

2.4 内联函数

    使用内联函数,编译器可能会因为获得的信息更多,从而对调用函数的优化做得更加深入和彻底,致使最终

的代码量变得更小。
   
    函数调用时执行的操作:参数压栈(逆序进行);保存返回值;保存维护调用函数栈帧信息的寄存器内容;

保存一些通用寄存器的内容。
    执行被调用函数结束:恢复通用寄存器的值。恢复保存调用函数栈帧信息的寄存器的值。移动栈指针,销毁

被调用函数栈帧,将保存的返回地址出栈,并赋给IP寄存器;通过移动栈指针,回收传给被调用函数的参数所占用的

空间。
   
    内联后可以降低“缺页”的几率。

    内联应该在开发后期引入,因为内联函数的修改,会引起所有用到它的编译单元重新编译。

    如果第三方程序修改了内联函数,而开发小组的程序已经发布,就会产生相当高的重新编译的成本。

    递归函数中,如果递归次数不能知道实际值,编译器会拒绝内联。即使知道递归次数,编译器会视次数大小

决定是否进行内联。

    内联函数是编译期行为,虚函数是执行期行为,因此编译器一般会拒绝对虚函数进行内联。但是下面两种情

况除外:
    1) 通过对象,而不是指向对象的指针或者对象的引用调用虚函数,这时编译器在编译器就已经知道对象的

确切类型。
    2) 虽然是指向对象的指针或者对象的引用调用虚函数,但是编译器能知道指针或者对象对应到的对象的确

切类型。(可深入)

    与register关键字性质类似,inline仅仅是给编译器一个“建议”,编译器完全可以视实际情况而忽略之。

    内联是编译期行为,宏是预处理期行为。编译器会对内联参数进行类型检查,宏则不会,宏的参数在宏体内

出现两次或者两次以上经常会产生副作用,尤其在宏体内对参数进行++或者--操作。宏肯定会被展开,而内联函数不

会。

    一个程序的唯一入口main()肯定不会被内联化。另外编译器合成的默认构造函数、拷贝构造函数、析构函数

,以及赋值运算符一般都会被内联化。



第三章 常用数据结构性能分析

3.1 常用数据结构性能分析
    1.数组:优点:查找方便(下标);添加删除元素时不会产生内存碎片;不需要考虑数据节点指针的存储
        确定:内存使用率低,可扩展性差
    2.链表:数组的所有数据项被存放在一段连续的存储空间,链表的数据项则可能随机的被分配到内存中的某

个位置
    3.哈希表:最常用的 除法映射 F(k) = k % D, k是数据节点的关键字, D是一个事先设定的常量,F(k)是

桶号
3.1.1 遍历 数组:下标  链表:指针 哈希表:结合 二叉树:3种
    // 前序遍历
template<class E>
void preTraverse( TreeNode<E> *node)
{
    if( node != NULL)
    {
        Dosomething( pNode);
        PreTraverse( pNode->Left);
        PreTraverse( pNode->Right);
    }
}

// 中序遍历
template<class E>
void InTraverse( TreeNode<E> *node)
{
    if( node != NULL)
    {
        InTraverse( pNode);
        DoSomething( pNode);
        InTraverse( pNode);
    }   
}

// 后序遍历
template<class E>
void PostTraverse( TreeNode<E> *node)
{
    if( node != NULL)
    {
        PostTravese( pNode);
        PostTravese( pNode);
        DoSomething( pNode);
    }
   
}

3.1.2 插入
    数组 O(1)-0(n) 链表O(1) 哈希表O(1)-O(M) 平衡二叉树O(Log 以2为底 n),完全非平衡二叉树O(n)
3.1.3 删除
    同上, 红黑二叉树可解决二叉树删除后的平衡性问题
3.1.5 查找
    数组  O(1)-0(n) 有序数组 二分查找法/折半查找法 O(Log 以2为底 n)
// 折半查找
template<class E>
E BinSearch( E array[], const E&value, int start, int end)
{
    // 判断起点和终点是否合法
    if( end - start < 0)
        return INVALID_INPUT;
    // 如果起点或者终点有符合要求的,直接将其返回
    if( value == array[start])
        return array[start];
    if( value == array[end])
        return array[end];
    while( end > (start+1) )
    {
        int temp = (start+end)/2;
        if( value == array[temp])
            return array[temp];
        if( array[temp] < value)
            start = temp;
        else
            end = temp
    }
    throw CANNOT_FIND;
}
    链表 O(n) 双向有序链表O(n/2) 跳转链表
    哈希表 O(m) m为桶的长度
    二叉树 平衡二叉树O(Log 以2为底 n),完全非平衡二叉树O(n)
// 哈希表查找算法
template<class E, class key >
ptrLinkNode HashTable::SearchNode( const key& k) const
{
    int idx = hashFun(k);    // 定位桶
    if( NULL == hash_array[idx]);
        return NULL;
    prtLinkNode p = hash_array[idx];
    while( p)
    {
        if( k == p->GetKey() )
            return p;
        p = p->Next();   
    }
    retun NULL;
}


3.2.1 动态数组
    优点:可分配的空间较大;使用灵活
    缺点: 空间分配效率比静态数组低,栈由机器系统提供,堆由C++函数库提供;容易造成内存泄露


第二部分 内存使用优化

第4章 操作系统的内存管理

4.1  window内存管理
    win32虚拟内存管理器为每一个win32进程提供了进程私有且基于页的4GB(32)位大小的线性虚拟地址空间


    1.“进程私有”意味着每个进程有只能访问属于自己的地址空间(父子进程除外)。进程运行的dll没有属

于自己的虚拟地址空间
    2.“基于页”是指虚拟地址空间被划分为多个称为“页”的单元,页的大小由底层处理器决定,x86中的页

的大小为4KB。虚拟地址空间的申请和释放,以及内存和磁盘数据传输或置换都是以页为最小单位进行的。
    3.“4GB”大小以为着地址取值范围可以从0x00000000到0xFFFFFFFF.win32将低区的2GB留给进程使用,高区

的2GB则留给系统使用。
    调页文件和缺页错误

4.1.1 使用虚拟内存
    页的三种状态 自由free,预留reserved,提交committed
    防止栈溢出:减小函数的嵌套层数,减少递归函数的使用,尽量不要在函数中使用太大的局部变量(使用堆

创建)。

4.1.2 访问虚拟内存时的处理流程
4.1.3 虚拟内存要物理地址的映射
4.1.4 虚拟内存空间使用状态记录
4.1.5 进程工作集
    要想提高程序的运行效率    1.尽量编写紧凑的代码
                2.尽量将那些会一起访问的数据(比如链表)放在一起
4.1.6     Win32内存相关API
    1、传统的CRT函数(malloc/free系列)平台无关性
    2. global heap/local heap函数(GlobalAlloc/LocalAlloc系列) 建议不使用
    3. 虚拟内存函数(VirtualAlloc/VirtualFree系列)为开发人员提供了最大自由度,适合于为了大型连续

的数据结构开辟空间
    4.内存映射文件函数(CreateFileMapping/MapViewOfFile系列),windows系统利用它来有效使用exe和dll

文件,来发人员则可以方便地操作硬盘文件,而不用考虑那些繁琐的文件I/O操作;运行在同一台机器上的多个进程

可以通过内存映射文件函数来共享数据(这也是同一台机器上进程间进行数据共享和通信的最有效率和最方便的方法


    5.堆内存函数(HwapCreate/HeapAlloc系列) 当程序需要动态创建多个小数据结构师,最为合适。CRT函数

就是基于堆内存函数实现的



4.2 Linux内存管理机制

第5章 动态内存管理
    5.5 智能指针
    Boost中的智能指针有3类,即scoped_ptr/scoped_array, share_ptr/share_array和weak_ptr
    1.scoped_ptr/scoped_array是最基本的智能指针实现,只满足最基本的需求
        与std::auto_ptr相同scoped_ptr也不能用于数组的C++标准容器库,需要用于数组时,要用到

scoped_array
    2.share_ptr/share_array则是boost提供的适用于“可拷贝”或“可赋值”的智能指针。boost::share_ptr

采用引用计数来实现共享语义,因此不可避免地存在循环引用的问题,而打破循环引用的常规方法就是所谓的“弱引

用”,即在使用之前要检查是否可开。
    3.weak_ptr用来解决上述问题。
    Loki库是另一个智能指针库,采用基于策略的实现方式。

第6章 内存池


第三篇 应用程序启动性能优化

第7章 动态链接与动态库
    windows动态链接库(DLL)和linux系统的动态共享对象(DSO)

第8章 程序启动过程

8.1 win32程序启动过程
    1.操作系统负责把程序从磁盘读入内存并建立相应的运行环境
    2.应用程序自身的初始化过程

编译链接过程:
    1.预编译展开一些宏
    2.为每一个.cxx源文件编译一个目标文件(.obj, .o),目标文件中至少会包含二进制代码段和数据段。还

会包含一个符号表,用于记录自己引用的符号及自己提供的public符号。
    3.编译器合成这些目标文件成一个库文件(.lib),同事解析可以找到的符号引用。也包含符号表,记录所

引用其他库德符号
    4.编译器负责把目标文件和所有需要引用的静态/动态链接库,即需要首先把动态把其他静态库合成到可执

行文件中。转换相应的符号为引用地址,然后确保所引用的其他动态链接库的符号存在。
   
复杂的程序启动过程:
    1.操作系统首先创建相应的进程并分配私有的进程空间,然后操作系统的加载器负责把可执行文件的数据段

和代码段映射到进程的虚拟内存空间中。
    2.加载器读入可执行程序的导入符号表,根据这些符号表可以查找出该可执行程序所依赖的动态链接库。
    3.加载器针对该程序依赖的没一个动态链接库调用loadLibrary
    4.初始化应用程序的全局变量,对于全局变量自动调用构造函数
    5.进入程序入口点开始执行

8.2 linux程序的启动过程

8.3 影响程序启动性能的因素

    程序启动的时间包含两个部分:IO操作消耗的时间(90%),程序代码运行占用的CPU时间
8.3.1 源代码因素
    1.全局变量初始化:越多越慢
    2.代码自身消耗的CPU:如果分需启动性能主要为IO问题,则可利用quantify等工具定位消耗CPU的热点代码

,加以优化
    3.无用代码导致可执行程序尺寸膨胀
8.3.2 动态链接库因素
    1.动态链接库数量及大小:bqb控制在10个左右?个数越小越好,尺寸越小越好
    2.启动时加载的操作系统的动态链接库:片面过多使用,可通过文件IO检测工具(Filemon)来发现
    3.调用代码时引发的缺页导致IO
    4.动态链接库初始化工作
    5.动态链接库符号可见性,导出要少,名字不要过于复杂
    6.动态链接库relocation问题
8.3.3 配置文件/资源文件因素
8.3.4 其他因素
    1.硬盘文件碎片问题
    2.操作系统于都(prefetch)功能

第9章 程序启动性能优化

9.1 优化程序启动性能的步骤
    1.定义启动性能问题,包括定义启动阶段的范围和设定启动性能的可行目标。
    2.在定义启动性能之后,需要通过测试(自动或者人工方式)来获得具体的启动性能数据。
    3.测试得到的稳定精确的数据是一切优化工作地基础,在此基础上,利用性能分析工具来确定应用程序启动

性能呢个的瓶颈或者影响启动性能的因素。
    4.针对特定的性能瓶颈或者影响因素设计具体的优化方案,并且实施优化或者实现优化方案
    5.针对优化后的应用程序重新回到第二不来确定优化后的测试结果,并且和预期目标比较,如果达成目标,

则优化完成;否则重复2-5

9.2 测试程序启动性能的方法
    1.保持相同的测试环境
    1.1 应用程序必需单独安装在一个空的分区中(下一次安装要格式化,保持IO恒定,可能会导致20%左右的

误差),若不能满足,则至少要进行碎片整理
    1.2 测试电脑的操作系统要尽量保持“清洁”,特别如防火墙,杀软之类的
    1.3 需要每次重新启动操作系统,在操作系统启动之后必需要等待一定得时间,直到cpu占用、网络占用、

IO活动都为0,内存占用恒定
    1.4 测试所用的不同版本的应用程序安装包应该有相同的来源
    1.5 测试电脑的电源管理功能,例如有的CPU有自动调整运行频率的功能,需强制蛇岛为固定频率运行,显

卡也有类似问题。
    1.6 在windowXP系统中测试时,要考虑操作系统的预读功能对于应用程序启动性能的影响,可以考虑删除

//windows//prefetch/下对应的预读只是文件或者暂时兼职操作系统的预读功能。
    2. 尽量利用自动测试

9.3 优化可执行文件和库文件
    9.3.1 减少动态链接库的数量 10个以内 提高15%
    1.强制修改代码
    2.合并动态链接库:方法介绍略
    9.3.2 减少动态链接库的尺寸
    1.通过编译优化选项 可缩小10%
    2.消除冗余代码
    9.3.3 优化可执行文件和库文件的代码布局
    1.获得函数调用的顺序文件(.PRF,一动态链接库为单位), 可使用sws工具(Smooth Working Set Tool)
    2.再把这些PRF文件传递给连接器,连接器会自动按照PRF文件把函数在动态链接库中的位置重新排列

9.4 优化源代码
    9.4.1 优化启动时读取的配置文件和帮助文件
    配置文件优化总的原则就是尽量合并
    9.4.2 预读频繁访问的文件
    1.方法一,需要构造相应的数据结构,把文件读入内存并且解析将其内容存放发哦一个内存数据结构中,以

供后续读取
    2.方法二,利用操作系统的文件缓存特性 window的MapViewOfFile, linux的mmap
    9.4.3 清楚产生exception的代码
    9.4.4 PreLoad
    构造一个独立的Preload程序来预先加载dll和其他数据, PreLoad程序可以作为哦后台服务启动
    9.4.5 延迟初始化
    将程序启动不需要的初始化工作退吃法哦程序启动之后完成 方法一 通过消息判断程序是否空闲,方法二:

在需要资源时初始化,采用singleton模式
    下面资源可以采用
    1. 启动时加载和初始化不需要的模块
    2. 一些帮助信息的初始化
    3.启动时不显示的菜单及工具栏相关资源
    4.必须由用户操作后才需要的资源
    9.4.6 多线程化启动
    如下特定适合多线程化启动:
    启动时需要加载大量的动态链接库,引发大量的IO操作。同时这些动态链接库的初始化函数又需要执行很多

计算密集型的操作,长时间占用CPU的时间。这时可以考虑把应用程序的IO等待时间和CPU占用时间交错并行处理,从

而减少总的启动时间

第10章 内存分析工具 IBM Rational Purify
    1.常见内存错误 内存泄露,数组越界读写,访问未初始化的内存,访问已经释放的内存

第11章 性能分析工具 IBM Rational Quantify

第12章 实时IO监测工具 FileMon

 

 

 

一些代码:

// 1.查看程序占用内存区 #include <stdio.h> #include <stdlib.h> int nGlobal = 100; int main(void) { char *pLocalString1 = "LocalString1"; // 书本有误 const char *pLocalString2 = "LocalString2"; // 如下 char *pLocalString3 = "LocalString3"; char **pLocalString4 = &pLocalString3; static int nLocalStatic = 100; int nLocal1 = 1; const int nLocalConst = 20; int *pNew = new int[5]; char *pMalloc = (char*)malloc(1); printf("global variable: 0x%x/n", &nGlobal); printf("static variable: 0x%x/n", &nLocalStatic); printf("local expression 1: 0x%x/n", pLocalString1); printf("local expression (const): 0x%x/n", pLocalString2); printf("/n"); printf("new: 0x%x/n", pNew); printf("malloc: 0x%x/n", pMalloc); printf("/n"); printf("local poniter(pNew): 0x%x/n", &pNew); printf("local poniter(pLocalString2): 0x%x/n", &pLocalString2); printf("local poniter(pLocalString1): 0x%x/n", &pLocalString1); printf("local variable(nLocal): 0x%x/n", &pMalloc); printf("local poniter(pmalloc): 0x%x/n", &pMalloc); printf("local const varible: 0x%x/n", &nLocalConst); return 0; } // 2.查看静态变量位置 #include <stdio.h> #include <stdlib.h> class A { public: int val; static int nCount; A() {nCount++;}; A~() {nCount--;}; } int A::nCount = 0; int main() { A a; B b; printf("number of A: %d/n", A::nCount); printf("non-static variable: 0x%x/n", &a.val); printf("non-static variable: 0x%x/n", &b.val); printf("static class member: 0x%x/n", &a.nCount); printf("static class member: 0x%x/n", &b.nCount); } // 3.对象生命周期 #include <stdio.h> class A { public: A() { printf("A create./n");} A(A& a) { printf("A create with copy/n");} ~A() { printf("A destroyed. /n");} }; A foo(A a) { A b; return b; } int main(void) { A a; a = foo( a); return 0; } // 4.临时对象 class Rational { piblic: /*explicit*/ Rational( int a=0, int b=1):m(a),n(b){} pravite: int m, n; } ... void foo() { Rational r; r = 100; // 看起来无法编译,但是编译器会将右边的100通过调用Rational::Rational(100,1)生成一个临时对象 ... } // 5.前序遍历 template<class E> void preTraverse( TreeNode<E> *node) { if( node != NULL) { Dosomething( pNode); PreTraverse( pNode->Left); PreTraverse( pNode->Right); } } // 中序遍历 template<class E> void InTraverse( TreeNode<E> *node) { if( node != NULL) { InTraverse( pNode); DoSomething( pNode); InTraverse( pNode); } } // 后序遍历 template<class E> void PostTraverse( TreeNode<E> *node) { if( node != NULL) { PostTravese( pNode); PostTravese( pNode); DoSomething( pNode); } } // 6.折半查找 template<class E> E BinSearch( E array[], const E&value, int start, int end) { // 判断起点和终点是否合法 if( end - start < 0) return INVALID_INPUT; // 如果起点或者终点有符合要求的,直接将其返回 if( value == array[start]) return array[start]; if( value == array[end]) return array[end]; while( end > (start+1) ) { int temp = (start+end)/2; if( value == array[temp]) return array[temp]; if( array[temp] < value) start = temp; else end = temp } throw CANNOT_FIND; } // 7.哈希表查找算法 template<class E, class key > ptrLinkNode HashTable::SearchNode( const key& k) const { int idx = hashFun(k); // 定位桶 if( NULL == hash_array[idx]); return NULL; prtLinkNode p = hash_array[idx]; while( p) { if( k == p->GetKey() ) return p; p = p->Next(); } retun NULL; } // 8.一个视频服务器的基本内存管理机制 // 数据帧的基本数据结构 typedef struct { unsigned short idcamera; // 摄像机ID unsigned long lenghl; // 数据长度 unsigned short width; // 图像宽度 unsigned short height; // 图像高度 unsigned char* data; // 图像数据地址 }Frame // 单台摄像机缓存模块定义 class CamBlock { Frame* _frameindex; // 帧索引表 unsigned char* _data; // 存放图像数据的缓存区,这是一个循环队列,FIFO原则,写入尾部,数据超出范围则从头覆盖 unsigned long _length; // 缓存区大小 int _idcam; // 对应摄像机的序号 unsigned short _numframe; // 可存放帧的数量 unsigned long _framepos // 最后一帧的位置 public: CamBlock( int id, unsigned long len, unsigned short numframes ): _data(NULL), _length(0), _idcam(-1), _numframes(0) { // 确保缓存区大小未超过阈值 if( len > MAX_LENGTH || numframes > MAX_FRAMES) throw; try{ // 为帧索引分配空间 _frameindex = new Frame[numframes]; // 为摄像机分配一块指定大小的缓存 _data = new unsigned char[len]; }catch(...){ // 如果分配失败,抛出例外 throw; } memset( _data, 0, len); // 将缓存清0 memset( _frameindex, 0, sizeof(Frame)*numframes); _length = len; _idcam = id; _numframed = numframes; }; ~CamBlock() { // 释放空间 delete []_data; delete []_frameindex; } // 存放一帧,根据索引表将视频数据存入缓存 BOOL SaveFrame( const Frame* frame); // 读取一帧,根据索引表定位到某一帧,读出并返回 BOOL ReadFrame( Frame* frame); } // CameraArray 类的声明 class CameraArray { typedef CamBlock *ptrBlock; ptrBlock* camera_bufs; // 摄像机视频缓存 unsigned short camera_num; // 当前一连上的摄像机的数目 unsigned short max_num; // camera_bufs的容量 unsigned short inc_num; // camera_bufs的增量 public: CameraArray( unsigned short max, unsigned short inc); ~CameraArray(); // 插入一台新的摄像机 CamBoock* InsertBlock( unsigned short idcam, unsigned long size, unsigned short numframes); // 删除一台摄像机记录 BOOL RemoveBlock( unsigned short idcam); private: // 根据摄像机ID返回它在数组中的位置 unsigned short GetPosition( unsigned shot idcam); }; // CameraArray类的构造函数 CameraArray::CameraArray( unsigned short max, unsigned short inc): camera_bufs(NULL), max_num(0), inc_num(0), camera_num(0) { // 如果输入参数越界,则抛出一个异常 if( max> MAX_CAMERAS || inc>MAX_INCREAMENT) throw; // 初始化camera_bufs数组 try{ camera_bufs = new PtrBlock[max]; }catch(...){ throw; } max_num = max; inc_num = inc; } // CameraArray类的析构函数 CameraArray::~CameraArray() { for( unsigned short i = 0; i<camera; ++i) // bqb:注意注意,这个地方要删除!!!!! delete camera_bufs[i]; delete []camera_bufs; } CamBlock* CameraArray::InsertBlock( unsigned short idcam, unsigned long size, unsigned short numframes) { // 在数组中找到合适的插入位置 unsigned short pos = GetPosition( idcam); // 已达到数组边界,需要扩大数组才能满足要求 if( camera_num == max_num) { // 定义一个指针,并指定其维数 PtrBlock* newbufs = NULL; try{ newbufs = new PtrBlock[ max_num + inc_num]; }catch(...){ throw; } // 将原数组中的数据拷贝到新数组 // 数组中的每个元素只是指向不同摄像机缓存块的指针 memcpy( newbufs, camera_bufs, maxnum*sizeof(PtrBlock) ); // 释放原数组的内存 delete []camera_bufs; max_num += inc_num; // 更新数组指针 camera_bufs = new newbufs; } if( pos != camera_num) { // 在数组中插入一个块,由于是camera id的递增顺序分配块的内存 // 因此需要将该位置以后的所有块的指针向后移一 // bqb:这个是不是有可能会把后面的一块内存给覆盖掉?不安全? memmove( camera_bufs + pos +1, camera_bufs + pos, (camera_num-pos)*sizeof(ptrBlock)); ++camera_num; CamBlock* newblock = new CamBlock( idcam, size, numframe); camera_bufs[pos] = pNode; return camera_bufs[pos]; } } BOOL CameraArray::RemoveBlock( unsigned short idcam) { // 在数组中找到了合适的插入位置 unsigned short pos = GetPosition( idcam); if( camera_num < 1) return FALSE; camera_num--; // 删除pos对应的内存块 PtrBlock delblock = camera_bufs[pos]; delete delblock; if( pos != camera_num) { // 将pos之后的元素向前移一位 memmove( camera_bufs + pos, camera_bufs + pos + 1, ( camera_num - pos)*sizeof(PtrBlock) ) } //如果数组中有过多的空闲元素空间,将其释放 if( max_num - camera_num > inc_num) { // 重新计算数组的长度 unsigned short len = ( ( camera_num/inc_num+1)*inc_num); // 定义新的数组指针,并指定其维数 PtrBlock* newbufs = NULL; try{ newbufs = new PtrBlock[len]; }catch{ throw; } // 将数组中的元素拷贝到新数组中 memcpy( newbufs, camera_bufs, camera_num*sizeof( PtrBlock)); delete[] camera_bufs; camera_bufs = newbufs; max_num = len; } return TRUE; }

你可能感兴趣的:(数据结构,C++,优化,读书,性能优化,编译器)