精华内容
下载资源
问答
  • 基于MATLAB的几种常用代码优化方法.pdf
  • 该压缩包整理了数学建模大赛常用的11种方法,包括 层次分析法,k-means,迪杰斯特拉,退火算法,弗洛伊德,灰色预测,种群竞争等方法
  • 以基本块为单位,进行运算上的推导优化。堪称妙!原来编译器这么强大!

    本次笔记内容:
    8-1 流图
    8-2 常用代码优化方法一
    8-3 常用代码优化方案二
    8-4 基本快的优化

    本节课幻灯片,见于我的 GitHub 仓库:第16讲 代码优化_1.pdf

    流图

    基本块(Basic Block)

    基本块是满足下列条件的最大连续三地址指令序列:

    • 控制流只能从基本块的第一个指令进入该块。也就是说,没有跳转到基本块中间或末尾指令的转移指令
    • 除了基本块的最后一个指令,控制流在离开基本块之前不会跳转或者停机

    基本块划分算法

    输入:

    • 三地址指令序列

    输出:

    • 输入序列对应的基本块列表,其中每个指令恰好被分配给一个基本块

    方法:

    • 首先,确定指令序列中哪些指令是首指令(leaders),即某个基本块的第一个指令
    1. 指令序列的第一个三地址指令是一个首指令
    2. 任意一个条件或无条件转移指令的目标指令是一个首指令
    3. 紧跟在一个条件或无条件转移指令之后的指令是一个首指令
    • 然后,每个首指令对应的基本块包括了从它自己开始,直到下一个首指令(不含)或者指令序列结尾之间的所有指令


    如上是快速排序法的部分源代码。

    根据跳转指令找首指令(如跳转指令后的一条指令)。

    流图(Flow Graphs)

    • 流图的结点是一些基本块
    • 从基本块B到基本块C之间有一条当且仅当基本块C的第一个指令可能紧跟在B的最后一条指令之后执行

    此时称B是C的前驱(predecessor) ,C是B的后继(successor)。

    有两种方式可以确认这样的边:

    • 有一个从B的结尾跳转到C的开头的条件或无条件跳转语句
    • 按照原来的三地址语句序列中的顺序,C紧跟在之B后,且B的结尾不存在无条件跳转语句



    感觉像是描述各个运算部分的关系。

    常用的代码优化方法(1)

    优化的分类

    • 机器无关优化:针对中间代码
    • 机器相关优化:针对目标代码
    • 局部代码优化:单个基本块范围内的优化
    • 全局代码优化:面向多个基本块的优化

    常用的优化方法

    • 删除公共子表达式
    • 删除无用代码
    • 常量合并
    • 代码移动
    • 强度削弱
    • 删除归纳变量

    ①删除公共子表达式

    公共子表达式:

    • 如果表达式x op y先前已被计算过,并且从先前的计算到现在,x op y中变量的值没有改变,那么x op y的这次出现就称为公共子表达式(common subexpression)


    将 B3 重构如黄色区域。


    由 B3 “逆流而上”,发现 4 ∗ i 4*i 4i 没有被修改过,则其是一个公共子表达式。

    进行了再次的画家如上。

    发现 a [ t 2 ] a[t_2] a[t2] a [ t 4 ] a[t_4] a[t4] 也是公共子表达式。




    a [ t 1 ] a[t_1] a[t1]能否作为公共子表达式?

    a [ t 1 ] a[t_1] a[t1]作为公共子表达式是不稳妥的,因为控制离开 B 1 B_1 B1进入 B 6 B_6 B6之前可能进入 B 5 B_5 B5,而 B 5 B_5 B5有对 a a a的赋值。



    关键问题:如何自动识别公共子表达式?

    会在后面的课程详细介绍。

    常用的代码优化方法(2)

    ②删除无用代码

    复制传播:常用的公共子表达式消除算法和其它一些优化算法会引入一些复制语句(形如x=y的赋值语句)


    复制传播:在复制语句x= y之后尽可能地用y代替x。

    无用代码(死代码Dead-Code):其计算结果永远不会被使用的语句。


    程序员不大可能有意引入无用代码,无用代码通常是因为前面执行过的某些转换而造成的。

    如何自动识别无用代码?

    也将在后文详细介绍。

    如上,通过删除公共子表达式删除无用代码,将 B5 与 B6 简化了不少。

    ③常量合并(Constant Folding)

    如果在编译时刻推导出一个表达式的值是常量,就可以使用该常量来替代这个表达式。该技术被称为常量合并

    ④代码移动(Code Motion)

    这个转换处理的是那些不管循环执行多少次都得到相同结果的表达式(即循环不变计算,loop-invariant computation) ,在进入循环之前就对它们求值。


    如何自动识别循环不变计算?

    循环不变计算的相对性

    对于多重嵌套的循环,循环不变计算是相对于某个循环而言的。可能对于更加外层的循环,它就不是循环不变计算。

    ⑤强度削弱(Strength Reduction)

    用较快的操作代替较慢的操作,如用加代替乘。

    循环中的强度削弱

    对于一个变量x ,如果存在一个正的或负的常数c使得每次x被赋值时它的值总增加c ,那么x就称为归纳变量(Induction Variable)。


    归纳变量可以通过在每次循环迭代中进行一次简单的增量运算(加法减法)来计算。

    ⑥删除归纳变量

    在沿着循环运行时,如果有一组归纳变量的值的变化保持步调一致,常常可以将这组变量删除为只剩一个。


    如上, i i i j j j 都无用了。

    基本块的优化

    很多重要的局部优化技术首先把一个基本块转换成为一个无环有向图(directed acyclic graph,DAG)。

    基本块的 DAG 表示

    基本块中的每个语句s都对应一个内部结点N:

    • 结点N的标号是s中的运算符;同时还有一个定值变量表被关联到N ,表示s是在此基本块内最晚对表中变量进行定值的语句
    • N的子结点是基本块中在s之前、最后一个对s所使用的运算分量进行定值的语句对应的结点。如果s的某个运算分量在基本块内没有在s之前被定值,则这个运算分量对应的子结点就是代表该运算分量初始值的叶结点(为区别起见,叶节点的定值变量表中的变量加上下脚标0)
    • 在为语句x=y+z构造结点N的时候,如果x已经在某结点M的定值变量表中,则从M的定值变量表中删除变量x

    例,有基本块:

    a = b + c
    b = a - d
    c = b + c
    d = a - d
    


    对于形如 x=y+z 的三地址指令,如果已经有一个结点表示 y+z,就不往 DAG 中增加新的结点,而是给已经存在的结点附加定值变量x。

    基于基本块的 DAG 删除无用代码

    从一个DAG上删除所有没有附加活跃变量(活跃变量是指其值可能会在以后被使用的变量)的根结点(即没有父结点的结点) 。重复应用这样的处理过程就可以从DAG中消除所有对应于无用代码的结点。

    数组元素赋值指令的表示


    如上,因为有可能出现 i = j i=j i=j ,因此不能轻易把 a [ i ] a[i] a[i] 算作公共子表达式。

    • 对于形如a[j] = y的三地址指令,创建一个运算符为“[]=”的结点,这个结点有3个子结点,分别表示a、j和y
    • 该结点没有定值变量表
    • 该结点的创建将杀死所有已经建立的、其值依赖于a的结点
    • 一个被杀死的结点不能再获得任何定值变量,也就是说,它不可能成为一个公共子表达式

    根据基本块的DAG可以获得一些非常有用的信息

    • 确定哪些变量的值在该基本块中赋值前被引用过:在DAG中创建了叶结点的那些变量
    • 确定哪些语句计算的值可以在基本块外被引用:在DAG构造过程中为语句s(该语句为变量x定值)创建的节点N,在DAG构造结束时x仍然是N的定值变量

    从 DAG 到基本块的重组

    对每个具有若干定值变量的节点,构造一个三地址语句来计算其中某个变量的值。

    倾向于把计算得到的结果赋给一个在基本块出口处活跃的变量(如果没有全局活跃变量的信息作为依据,就要假设所有变量都在基本块出口处活跃,但是不包含编译器为处理表达式而生成的临时变量)。

    如果结点有多个附加的活跃变量,就必须引入复制语句,以便给每一个变量都赋予正确的值。


    构建 DAG 如右边。常量直接标记出来。

    最终,根据 DAG 得到优化后的基本块如下:

    D = A + C
    E = A * C
    F = E + D
    L = 15 + F
    
    展开全文
  • 常用代码优化方法

    千次阅读 2019-04-26 17:18:00
    尽量减小同步作用范围, synchronized 方法,避免使用synchronized 代码块 用ThreadLocal 缓存线程不安全的对象,SimpleDateFormat 尽量使用延迟加载 尽量减少使用反射,必须用加缓存 尽量使用连接池、...
    1. 尽量重用对象,不要循环创建对象,比如:for 循环字符串拼接(不在 for中使用+拼接,先new 一个StringBuilder再在 for 里 append)
    2. 容器类初始化的地时候指定长度
    3. ArrayList(底层数组)随机遍历快,LinkedList(底层双向链表)添加删除快
    4. 集合遍历尽量减少重复计算
    5. 使用 Entry 遍历 Map
    6. 大数组复制使用System.arraycopy
    7. 尽量使用基本类型而不是包装类型
    8. 不要手动调用 System.gc()
    9. 及时消除过期对象的引用,防止内存泄漏
    10. 尽量使用局部变量,减小变量的作用域
    11. 尽量使用非同步的容器ArraryList ,避免使用Vector
    12. 尽量减小同步作用范围, synchronized 方法,避免使用synchronized 代码块
    13. 用ThreadLocal 缓存线程不安全的对象,SimpleDateFormat
    14. 尽量使用延迟加载
    15. 尽量减少使用反射,必须用加缓存
    16. 尽量使用连接池、线程池、对象池、缓存
    17. 及时释放资源, I/O 流、Socket、数据库连接
    18. 慎用异常,不要用抛异常来表示正常的业务逻辑
    19. String 操作尽量少用正则表达式
    20. 日志输出注意使用不同的级别
    21. 日志中参数拼接使用占位符 

        log.info("orderId:" + orderId); 不推荐 

        log.info("orderId:{}", orderId); 推荐

    展开全文
  • 一些代码优化方法

    千次阅读 2019-03-05 14:27:31
    当然不是,C++层次一样可以作代码优化,其中有些常常是意想不到的。在C++层次进行优化,比在汇编层次优化具有更好的移植性,应该是优化中的首选做法。 1 确定浮点型变量和表达式是 float 型 为了让编...

     

    原文出处:https://blog.csdn.net/zssureqh/article/details/7606406

     

    谈到优化,很多人都会直接想到汇编。难道优化只能在汇编层次吗?当然不是,C++层次一样可以作代码优化,其中有些常常是意想不到的。在C++层次进行优化,比在汇编层次优化具有更好的移植性,应该是优化中的首选做法。

    1 确定浮点型变量和表达式是 float 型

    为了让编译器产生更好的代码(比如说产生3DNow! 或SSE指令的代码),必须确定浮点型变量和表达式是 float 型的。要特别注意的是,以 "F" 或 "f" 为后缀(比如:3.14f)的浮点常量才是 float 型,否则默认是 double 型。为了避免 float 型参数自动转化为 double,请在函数声明时使用 float。

    2 使用32位的数据类型

    编译器有很多种,但它们都包含的典型的32位类型是:int,signed,signed int,unsigned,unsigned int,long,signed long,long int,signed long int,unsigned long,unsigned long int。尽量使用32位的数据类型,因为它们比16位的数据甚至8位的数据更有效率。

    3 明智使用有符号整型变量

    在很多情况下,你需要考虑整型变量是有符号还是无符号类型的。比如,保存一个人的体重数据时不可能出现负数,所以不需要使用有符号类型。但是,如果是要保存温度数据,就必须使用到有符号的变量。

    在许多地方,考虑是否使用有符号的变量是必要的。在一些情况下,有符号的运算比较快;但在一些情况下却相反。

    比如:整型到浮点转化时,使用大于16位的有符号整型比较快。因为x86构架中提供了从有符号整型转化到浮点型的指令,但没有提供从无符号整型转化到浮点的指令。看看编译器产生的汇编代码:

    不好的代码:

    编译前

    编译后

    double x;

    unsigned int i;

    x = i;

    mov [foo + 4], 0

    mov eax, i

    mov [foo], eax

    flid qword ptr [foo]

    fstp qword ptr [x]

    上面的代码比较慢。不仅因为指令数目比较多,而且由于指令不能配对造成的FLID指令被延迟执行。最好用以下代码代替:

    推荐的代码:

    编译前

    编译后

    double x;

    int i;

    x = i;

    fild dword ptr [i]

    fstp qword ptr [x]

    在整数运算中计算商和余数时,使用无符号类型比较快。以下这段典型的代码是编译器产生的32位整型数除以4的代码:

    不好的代码

    推荐的代码

    编译前

    编译后

    编译前

    编译后

    int i;

    i = i / 4;

    mov eax, i

    cdq

    and edx, 3

    add eax, edx

    sar eax, 2

    mov i, eax

    unsigned int i;

    i = i / 4;

    shr i, 2

    总结:

    无符号类型用于:

    除法和余数

    循环计数

    数组下标

    有符号类型用于:

    整型到浮点的转化

    4 while VS. for

    在编程中,我们常常需要用到无限循环,常用的两种方法是while (1) 和 for (;;)。这两种方法效果完全一样,但那一种更好呢?然我们看看它们编译后的代码:

    编译前

    编译后

    while (1);

    mov eax,1

    test eax,eax

    je foo+23h

    jmp foo+18h

    编译前

    编译后

    for (;;);

    jmp foo+23h

    一目了然,for (;;)指令少,不占用寄存器,而且没有判断跳转,比while (1)好。

    5 使用数组型代替指针型

    使用指针会使编译器很难优化它。因为缺乏有效的指针代码优化的方法,编译器总是假设指针可以访问内存的任意地方,包括分配给其他变量的储存空间。所以为了编译器产生优化得更好的代码,要避免在不必要的地方使用指针。一个典型的例子是访问存放在数组中的数据。C++ 允许使用操作符 [] 或指针来访问数组,使用数组型代码会让优化器减少产生不安全代码的可能性。比如,x[0] 和x[2] 不可能是同一个内存地址,但 *p 和 *q 可能。强烈建议使用数组型,因为这样可能会有意料之外的性能提升。

    不好的代码

    推荐的代码

    typedef struct

    {

        float x,y,z,w;

    } VERTEX;

    typedef struct

    {

        float m[4][4];

    } MATRIX;

    void XForm(float* res, const float* v, const float* m, int nNumVerts)

    {

        float dp;

        int i;

        const VERTEX* vv = (VERTEX *)v;

        for (i = 0; i < nNumVerts; i++)

        {

           dp = vv->x * *m ++;

           dp += vv->y * *m ++;

           dp += vv->z * *m ++;

           dp += vv->w * *m ++;

           *res ++ = dp;    // 写入转换了的 x

           dp = vv->x * *m ++;

           dp += vv->y * *m ++;

           dp += vv->z * *m ++;

           dp += vv->w * *m ++;

           *res ++ = dp;     // 写入转换了的 y

           dp = vv->x * *m ++;

           dp += vv->y * *m ++;

           dp += vv->z * *m ++;

           dp += vv->w * *m ++;

           *res ++ = dp;     // 写入转换了的 z

           dp = vv->x * *m ++;

           dp += vv->y * *m ++;

           dp += vv->z * *m ++;

           dp += vv->w * *m ++;

           *res ++ = dp;    // 写入转换了的 w

           vv ++;           // 下一个矢量

           m -= 16;

        }

    }

    typedef  struct

    {

        float x,y,z,w;

    } VERTEX;

    typedef  struct

    {

        float m[4][4];

    } MATRIX;

    void XForm (float* res, const float* v, const float* m, int nNumVerts)

    {

        int i;

        const VERTEX* vv = (VERTEX*)v;

        const MATRIX* mm = (MATRIX*)m;

        VERTEX* rr = (VERTEX*)res;

        for (i = 0; i < nNumVerts; i++)

        {

           rr->x = vv->x * mm->m[0][0] + vv->y * mm->m[0][1]

                  + vv->z * mm->m[0][2] + vv->w * mm->m[0][3];

           rr->y = vv->x * mm->m[1][0] + vv->y * mm->m[1][1]

                  + vv->z * mm->m[1][2] + vv->w * mm->m[1][3];

           rr->z = vv->x * mm->m[2][0] + vv->y * mm->m[2][1]

                  + vv->z * mm->m[2][2] + vv->w * mm->m[2][3];

           rr->w = vv->x * mm->m[3][0] + vv->y * mm->m[3][1]

                  + vv->z * mm->m[3][2] + vv->w * mm->m[3][3];

        }

    }

    注意: 源代码的转化是与编译器的代码发生器相结合的。从源代码层次很难控制产生的机器码。依靠编译器和特殊的源代码,有可能指针型代码编译成的机器码比同等条件下的数组型代码运行速度更快。明智的做法是在源代码转化后检查性能是否真正提高了,再选择使用指针型还是数组型。

    6 充分分解小的循环

    要充分利用CPU的指令缓存,就要充分分解小的循环。特别是当循环体本身很小的时候,分解循环可以提高性能。BTW:很多编译器并不能自动分解循环。

    不好的代码

    推荐的代码

    // 3D转化:把矢量 V 和 4x4 矩阵 M 相乘

    for (i = 0; i < 4; i ++)

    {

        r[i] = 0;

        for (j = 0; j < 4; j ++)

        {

           r[i] += M[j][i]*V[j];

        }

    }

    r[0] = M[0][0]*V[0] + M[1][0]*V[1] + M[2][0]*V[2] + M[3][0]*V[3];

    r[1] = M[0][1]*V[0] + M[1][1]*V[1] + M[2][1]*V[2] + M[3][1]*V[3];

    r[2] = M[0][2]*V[0] + M[1][2]*V[1] + M[2][2]*V[2] + M[3][2]*V[3];

    r[3] = M[0][3]*V[0] + M[1][3]*V[1] + M[2][3]*V[2] + M[3][3]*v[3];

    7 避免没有必要的读写依赖

    当数据保存到内存时存在读写依赖,即数据必须在正确写入后才能再次读取。虽然AMD Athlon等CPU有加速读写依赖延迟的硬件,允许在要保存的数据被写入内存前读取出来,但是,如果避免了读写依赖并把数据保存在内部寄存器中,速度会更快。在一段很长的又互相依赖的代码链中,避免读写依赖显得尤其重要。如果读写依赖发生在操作数组时,许多编译器不能自动优化代码以避免读写依赖。所以推荐程序员手动去消除读写依赖,举例来说,引进一个可以保存在寄存器中的临时变量。这样可以有很大的性能提升。下面一段代码是一个例子:

    不好的代码

    推荐的代码

    float x[VECLEN], y[VECLEN], z[VECLEN];

    .......

    for (unsigned int k = 1; k < VECLEN; k ++)

    {

        x[k] = x[k-1] + y[k];

    }

    for (k = 1; k < VECLEN; k++)

    {

        x[k] = z[k] * (y[k] - x[k-1]);

    }

    float x[VECLEN], y[VECLEN], z[VECLEN];

    .......

    float t(x[0]);

    for (unsigned int k = 1; k < VECLEN; k ++)

    {

        t = t + y[k];

        x[k] = t;

    }

    t = x[0];

    for (k = 1; k < VECLEN; k ++)

    {

        t = z[k] * (y[k] - t);

        x[k] = t;

    }

    8 Switch 的用法

    Switch 可能转化成多种不同算法的代码。其中最常见的是跳转表和比较链/树。推荐对case的值依照发生的可能性进行排序,把最有可能的放在第一个,当switch用比较链的方式转化时,这样可以提高性能。此外,在case中推荐使用小的连续的整数,因为在这种情况下,所有的编译器都可以把switch 转化成跳转表。

    不好的代码

    推荐的代码

    int days_in_month, short_months, normal_months, long_months;

    .......

    switch (days_in_month)

    {

        case 28:

        case 29:

           short_months ++;

           break;

        case 30:

           normal_months ++;

           break;

        case 31:

           long_months ++;

           break;

        default:

           cout << "month has fewer than 28 or more than 31 days" << endl;

           break;

    }

    int days_in_month, short_months, normal_months, long_months;

    .......

    switch (days_in_month)

    {

        case 31:

           long_months ++;

           break;

        case 30:

           normal_months ++;

           break;

        case 28:

        case 29:

           short_months ++;

           break;

        default:

           cout << "month has fewer than 28 or more than 31 days" << endl;

           break;

    }

    9 所有函数都应该有原型定义

    一般来说,所有函数都应该有原型定义。原型定义可以传达给编译器更多的可能用于优化的信息。

    10 尽可能使用常量(const)

    尽可能使用常量(const)。C++ 标准规定,如果一个const声明的对象的地址不被获取,允许编译器不对它分配储存空间。这样可以使代码更有效率,而且可以生成更好的代码。

    11 提升循环的性能

    要提升循环的性能,减少多余的常量计算非常有用(比如,不随循环变化的计算)。

    不好的代码(在for()中包含不变的if())

    推荐的代码

    for( i ... )

    {

        if( CONSTANT0 )

        {

           DoWork0( i ); // 假设这里不改变CONSTANT0的值

        }

        else

        {

           DoWork1( i ); // 假设这里不改变CONSTANT0的值

        }

    }

    if( CONSTANT0 )

    {

        for( i ... )

        {

           DoWork0( i );

        }

    }

    else

    {

        for( i ... )

        {

           DoWork1( i );

        }

    }

    如果已经知道if()的值,这样可以避免重复计算。虽然不好的代码中的分支可以简单地预测,但是由于推荐的代码在进入循环前分支已经确定,就可以减少对分支预测的依赖。

    12 把本地函数声明为静态的(static)

    如果一个函数在实现它的文件外未被使用的话,把它声明为静态的(static)以强制使用内部连接。否则,默认的情况下会把函数定义为外部连接。这样可能会影响某些编译器的优化——比如,自动内联。

    13 考虑动态内存分配

    动态内存分配(C++中的"new")可能总是为长的基本类型(四字对齐)返回一个已经对齐的指针。但是如果不能保证对齐,使用以下代码来实现四字对齐。这段代码假设指针可以映射到 long 型。

    例子

     

    double* p = (double*)new BYTE[sizeof(double) * number_of_doubles+7L];

    double* np = (double*)((long(p) +7L) & –8L);

     

    现在,你可以使用 np 代替 p 来访问数据。注意:释放储存空间时仍然应该用delete p。

    14 使用显式的并行代码

    尽可能把长的有依赖的代码链分解成几个可以在流水线执行单元中并行执行的没有依赖的代码链。因为浮点操作有很长的潜伏期,所以不管它被映射成 x87 或 3DNow! 指令,这都很重要。很多高级语言,包括C++,并不对产生的浮点表达式重新排序,因为那是一个相当复杂的过程。需要注意的是,重排序的代码和原来的代码在代数上一致并不等价于计算结果一致,因为浮点操作缺乏精确度。在一些情况下,这些优化可能导致意料之外的结果。幸运的是,在大部分情况下,最后结果可能只有最不重要的位(即最低位)是错误的。

    不好的代码

    推荐的代码

    double a[100], sum;

    int i;

    sum =0.0f;

    for (i=0; i<100; i++)

        sum += a[i];

    double a[100], sum1, sum2, sum3, sum4, sum;

    int i;

    sum1 = sum2 = sum3 = sum4 = 0.0;

    for (i = 0; i < 100; i += 4)

    {

        sum1 += a[i];

        sum2 += a[i+1];

        sum3 += a[i+2];

        sum4 += a[i+3];

    }

    sum = (sum4+sum3)+(sum1+sum2);

    要注意的是:使用4 路分解是因为这样使用了4阶段流水线浮点加法,浮点加法的每一个阶段占用一个时钟周期,保证了最大的资源利用率。

    15 提出公共子表达式

    在某些情况下,C++编译器不能从浮点表达式中提出公共的子表达式,因为这意味着相当于对表达式重新排序。需要特别指出的是,编译器在提取公共子表达式前不能按照代数的等价关系重新安排表达式。这时,程序员要手动地提出公共的子表达式(在VC.net里有一项“全局优化”选项可以完成此工作,但效果就不得而知了)。

    不好的代码

    推荐的代码

    float a, b, c, d, e, f;

    ....

    e = b * c / d;

    f = b / d * a;

    float a, b, c, d, e, f;

    ....

    const float t(b / d);

    e = c * t;

    f = a * t;

    不好的代码

    推荐的代码

    float a, b, c, e, f;

    ....

    e = a / c;

    f = b / c;

    float a, b, c, e, f;

    ....

    const float t(1.0f/ c);

    e = a * t;

    f = b * t;

    16 结构体成员的布局

    很多编译器有“使结构体字,双字或四字对齐”的选项。但是,还是需要改善结构体成员的对齐,有些编译器可能分配给结构体成员空间的顺序与他们声明的不同。但是,有些编译器并不提供这些功能,或者效果不好。所以,要在付出最少代价的情况下实现最好的结构体和结构体成员对齐,建议采取这些方法:

    17 按类型长度排序

    把结构体的成员按照它们的类型长度排序,声明成员时把长的类型放在短的前面。

    18 把结构体填充成最长类型长度的整倍数

    把结构体填充成最长类型长度的整倍数。照这样,如果结构体的第一个成员对齐了,所有整个结构体自然也就对齐了。下面的例子演示了如何对结构体成员进行重新排序:

    不好的代码,普通顺序

    推荐的代码,新的顺序并手动填充了几个字节

    struct

    {

        char a[5];

        long k;

        double x;

    } baz;

    struct

    {

        double x;

        long k;

        char a[5];

        char pad[7];

    } baz;

    这个规则同样适用于类的成员的布局。

    19 按数据类型的长度排序本地变量

    当编译器分配给本地变量空间时,它们的顺序和它们在源代码中声明的顺序一样,和上一条规则一样,应该把长的变量放在短的变量前面。如果第一个变量对齐了,其它变量就会连续的存放,而且不用填充字节自然就会对齐。有些编译器在分配变量时不会自动改变变量顺序,有些编译器不能产生4字节对齐的栈,所以4字节可能不对齐。下面这个例子演示了本地变量声明的重新排序:

    不好的代码,普通顺序

    推荐的代码,改进的顺序

    short ga, gu, gi;

    long foo, bar;

    double x, y, z[3];

    char a, b;

    float baz;

    double z[3];

    double x, y;

    long foo, bar;

    float baz;

    short ga, gu, gi;

    20 避免不必要的整数除法

    整数除法是整数运算中最慢的,所以应该尽可能避免。一种可能减少整数除法的地方是连除,这里除法可以由乘法代替。这个替换的副作用是有可能在算乘积时会溢出,所以只能在一定范围的除法中使用。

    不好的代码

    推荐的代码

    int i, j, k, m;

    m = i / j / k;

    int i, j, k, m;

    m = i / (j * k);

    21 把频繁使用的指针型参数拷贝到本地变量

    避免在函数中频繁使用指针型参数指向的值。因为编译器不知道指针之间是否存在冲突,所以指针型参数往往不能被编译器优化。这样是数据不能被存放在寄存器中,而且明显地占用了内存带宽。注意,很多编译器有“假设不冲突”优化开关(在VC里必须手动添加编译器命令行/Oa或/Ow),这允许编译器假设两个不同的指针总是有不同的内容,这样就不用把指针型参数保存到本地变量。否则,请在函数一开始把指针指向的数据保存到本地变量。如果需要的话,在函数结束前拷贝回去。

    不好的代码

    推荐的代码

    // 假设 q != r

    void isqrt(unsigned long a, unsigned long* q, unsigned long* r)

    {

        *q = a;

        if (a > 0)

        {

           while (*q > (*r = a / *q))

           {

               *q = (*q + *r) >> 1;

           }

        }

        *r = a - *q * *q;

    }

    // 假设 q != r

    void isqrt(unsigned long a, unsigned long* q, unsigned long* r)

    {

        unsigned long qq, rr;

        qq = a;

        if (a > 0)

        {

           while (qq > (rr = a / qq))

           {

               qq = (qq + rr) >> 1;

           }

        }

        rr = a - qq * qq;

        *q = qq;

        *r = rr;

    }

    22 赋值与初始化

    先看看以下代码:

    class CInt

    {

        int m_i;

    public:

        CInt(int a = 0):m_i(a) { cout << "CInt" << endl; }

        ~CInt() { cout << "~CInt" << endl; }

        CInt operator + (const CInt& a) { return CInt(m_i + a.GetInt()); }

        void SetInt(const int i) { m_i = i; }

        int GetInt() const          { return m_i; }

    };

    不好的代码

    推荐的代码

    void main()

    {

        CInt a, b, c;

        a.SetInt(1);

        b.SetInt(2);

        c = a + b;

    }

    void main()

    {

        CInt a(1), b(2);

        CInt c(a + b);

    }

    这两段代码所作的事都一样,但那一个更好呢?看看输出结果就会发现,不好的代码输出了四个"CInt"和四个"~CInt",而推荐的代码只输出三个。也就是说,第二个例子比第一个例子少生成一次临时对象。Why? 请注意,第一个中的c用的是先声明再赋值的方法,第二个用的是初始化的方法,它们有本质的区别。第一个例子的"c = a + b"先生成一个临时对象用来保存a + b的值,再把该临时对象用位拷贝的方法给c赋值,然后临时对象被销毁。这个临时对象就是那个多出来的对象。第二个例子直接用拷贝构造函数的方法对c初始化,不产生临时对象。所以,尽量在需要使用一个对象时才声明,并用初始化的方法赋初值。

    23 尽量使用成员初始化列表

    在初始化类的成员时,尽量使用成员初始化列表而不是传统的赋值方式。

    不好的代码

    推荐的代码

    class CMyClass

    {

        string strName;

    public:

        CMyClass(const string& str);

    };

    CMyClass::CMyClass(const string& str)

    {

        strName = str;

    }

    class CMyClass

    {

        string strName;

        int i;

       

    public:

        CMyClass(const string& str);

    };

    CMyClass::CMyClass(const string& str)

        : strName(str)

    {

    }

    不好的例子用的是赋值的方式。这样,strName会先被建立(调用了string的默认构造函数),再由参数str赋值。

    而推荐的例子用的是成员初始化列表,strName直接构造为str,少调用一次默认构造函数,还少了一些安全隐患

     

    展开全文
  • 常见的代码优化技术

    千次阅读 2019-10-01 16:35:25
    常见的代码优化技术:复写传播,删除死代码, 强度削弱,归纳变量删除,代码外提 1.复写传播: 复写语句:形式为f = g 的赋值 优化过程中会大量引入复写 复写传播变换的做法是在复写语句f = g后,尽可能用g代表f ...

    常见的代码优化技术有:复写传播,删除死代码, 强度削弱,归纳变量删除,代码外提

    1.复写传播:

    复写语句:形式为f = g 的赋值
    优化过程中会大量引入复写
    复写传播变换的做法是在复写语句f = g后,尽可能用g代表f
    复写传播变换本身并不是优化,但它给其他优化带来机会
    常量合并(编译时可完成的计算)

    2.死代码删除
    死代码是指计算的结果决不被引用的语句
    一些优化变换可能会引起死代码
    3.代码外提
    代码外提是循环优化的一种

    例:

    在这里插入图片描述

    4.归纳变量删除

    在这里插入图片描述
    j和t4的值步伐一致地变化,这样的变量叫作归纳变量
    在循环中有多个归纳变量时,也许只需要留下一个
    这个操作由归纳变量删除过程来完成
    对本例可以先做强度削弱,它给删除归纳变量创造机会
    5.强度削弱
    强度削弱的本质是把强度大的运算换算成强度小的运算,例如将乘法换成加法运算。

    展开全文
  • 细读完这篇文章,够你优化大半天的了,关于JS优化方法大都脱离不了这三种方法。 (网页总大小为155.k,而JS就占了100.3K) 是时候优化下JS了 关于JS优化的文章已经很多了,大多技术性很强,像什么变量、字符串、...
  • 给你的网站加上代码统计!常用方法是直接加统计代码到网页,但你的网页数量很多呢?
  • 常用的SQL优化办法哪些

    千次阅读 2019-08-15 20:54:05
    数据库优化的几个方面 SQL以及索引的优化是最重要的。 要根据一些范式来进行表结构的设计。 系统配置的优化。 硬件优化。 sql语句的优化 可以通过打开Mysql中的慢查询日志来定位问题的sql语句 慢查询日志相关...
  • JAVA代码优化

    千次阅读 2018-10-21 23:27:45
    一、类、方法、变量尽量指定final修饰 public static void test(){ final String x=&amp;amp;quot;1&amp;amp;quot;; final String y=x+&amp;amp;quot;2&amp;amp;quot;; String z=x+y; ...
  • 目前常用优化标准测试函数及MATLAB代码 目前常用优化标准测试函数及MATLAB代码 目前常用优化标准测试函数及MATLAB代码 目前常用优化标准测试函数及MATLAB代码
  • MATLAB中代码优化的两种方法

    千次阅读 2020-05-28 16:26:27
    MATLAB中的代码优化有两种重要的方法:预分配组和向量化循环。 我们举一个简单的例子来看,创建一个MATLAB函数来计算f(x) = sin(x / 100π): function y = sinfun1(M) x = 0: M - 1; for k = 1: numel(x) y(k) = ...
  • Java代码优化要注重细节优化,一个两个的细节的优化,产生的效果不大,但是如果处处都能注意代码优化,对代码减少体积、提高代码运行效率是巨大帮助的,还能在一定程度上避免未知错误,常用的Java代码优化细节如下...
  • JS代码优化的几种方式

    千次阅读 2016-08-05 20:45:13
    模板中引用的JS文件越多,打开速度越慢,这点我深体会,不信你看看卢松松博客首页,使劲优化后依然100K的文件。细读完这篇文章,够你优化大半天的了,关于JS优化方法大都脱离不了这三种方法。 (网页总...
  • C++程序代码优化方法

    千次阅读 2018-10-08 16:18:06
    1、选择合适的算法和数据结构  ...对于大部分的编译器,使用指针比使用数组生成的代码更短,执行效率更高。  在许多种情况下,可以用指针运算代替数组索引,这样做常常能产生又快又短的代码。与数组索引相...
  • 常用SQL优化方法

    万次阅读 多人点赞 2018-04-10 07:55:09
    2、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 3、应尽量避免在 where 子句中对字段进行 null值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id...
  • java代码优化

    千次阅读 热门讨论 2018-06-09 14:16:45
    自己也积累了一些代码优化方面的心得,一个好的代码是体现程序员水平的最直观体现,下面来看一下代码方面比较常见的优化点(本文在书写过程中也参考了一些其他文章,在这里就不一一赘述,内容也比较浅显,忘大神轻喷...
  • SQL优化常用的几种方法

    万次阅读 多人点赞 2020-06-29 14:42:42
    在使用JPA时常常出现业务复杂不方便使用名称解析的情况,这时可采用原生SQL来实现,SQL在请求并发数量较多时效率会影响系统的整体效率,在此记录一下sql优化常用几种方法。 二、优化方法 1、对查询进行优化,...
  • c代码优化常用的几招 Word文档 第一招:以空间换时间 第二招: 使用宏而不是函数 第三招:数学方法解决问题 …………
  • 优化其实可以在编译的各个阶段进行,但最主要的一类优化是在目标代码生成以前,对语法分析、语义分析后产生的中间代码进行优化。这是因为中间代码的形式不依赖于具体的...中间代码优化中,很多技术和手段可以应用。
  • 一、代码优化的阶段 欲提高源程序的运行速度,需要经过几个阶段的优化: 用户对源程序进行优化(和编译器无关,与coder设计的算法有关) 编译器前端对中间代码进行优化 编译器后端对目标代码进行优化 两个编译器...
  • 优化计算方法常用程序汇编,这个资料不错。希望对您帮助。。
  • 代码级性能优化方法小结

    千次阅读 2019-06-14 15:41:19
    一、概述 另外针对Ffmpeg多进程转码时,可以...LinuxNAPI机制,会自适应根据数据量,调整收包模式。 二、搜集资料 译器内部函数的配置:《Generate Intrinsic Functions》 预读指令读内存参考:《memory ...
  • 编译原理代码优化

    2012-11-28 18:43:49
    编译原理 代码优化 详细介绍编译原理的代码优化方法
  • 编译原理过程简述及中间代码优化

    千次阅读 2017-09-28 17:21:23
    一、编译过程图示如下:词法分析作用:找出单词 。...二、中间代码优化所谓代码优化是指对程序代码进行等价(指不改变程序的运行结果)变换。程序代码可以是中间代码(如四元式代码),也可以是目标代码。
  • sql优化常用的几种方法,19种最有效的sql优化技巧 本文我们来谈谈项目中常用的MySQL优化方法,共19条,具体如下: 1、EXPLAIN 做MySQL优化,我们要善用EXPLAIN查看SQL执行计划。 下面来个简单的示例,标注(1...
  • Java代码如何进行优化

    千次阅读 2018-04-02 15:34:03
    Java代码优化要注重细节优化,一个两个的细节的优化,产生的效果不大,但是如果处处都能注意代码优化,对代码减少体积、提高代码运行效率是巨大帮助的,还能在一定程度上避免未知错误,常用的Java代码优化细节如下...
  • 常用优化方法

    千次阅读 2018-05-18 14:14:15
    熟悉机器学习的童鞋都知道,优化方法是其中一个非常重要的话题,最常见的情形就是利用目标函数的导数通过多次迭代来求解无约束最优化问题。实现简单,coding方便,是训练模型的必备利器之一。 2. 几个数学概念 1)...
  • 编译原理之代码优化

    2020-04-07 11:52:20
    代码优化定义 并且在中间代码到目标代码的过程中,许多数据科学家发明了很多可以提高代码运行效率的算法,这个就是编译原理中重要的代码优化部分 代码优化的分类: 机器无关优化 :针对中间代码 机器相关优化 :...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 386,689
精华内容 154,675
关键字:

常用的代码优化的方式有什么