精华内容
下载资源
问答
  • 优秀程序设计遵循原则

    千次阅读 2017-09-17 21:37:41
    可配置 (参数或者配置文件的形式);高效 (经过优化);简洁(一目了然 见名知意);...模块化,遵循单一责任原则;多用接口少用类;优先选择不可变的对象;经过充分的测试;尽量简短,调用成熟的库,避免bug

    可配置 (参数或者配置文件的形式);

    高效 (经过优化);

    简洁(一目了然 见名知意);

    规范化(代码有一定的规范和标准),可读性;

    具有很强的防错性;

    巧用设计模式;

    模块化,遵循单一责任原则;

    多用接口少用类;

    优先选择不可变的对象;

    经过充分的测试;

    尽量简短,调用成熟的库,避免bug;

    多线程安全;

    面向接口编程,但要避免接口泛化;

    尽量不要重写父类的已经实现了的方法,可以用接口等其他方法绕过;

    高内聚,低耦合;

    对扩展开放,对修改关闭;

    多用组合、聚合,少用继承

    展开全文
  • 程序优化的内容和原则

    千次阅读 2005-04-10 20:40:00
    程序优化的内容和原则程序优化,通常是指优化程序代码或程序执行速度。优化代码和优化速度实际上是一个矛盾的统一,一般是优化了代码的尺寸,就会带来执行时间的增加,如果优化程序的执行速度,通常会带来增加...

    程序优化的内容和原则

    对程序的优化,通常是指优化程序代码或程序执行速度。优化代码和优化速度实际上是一个矛盾的统一,一般是优化了代码的尺寸,就会带来执行时间的增加,如果优化程序的执行速度,通常会带来增加代码的副作用,很难鱼与熊掌兼得,只能在设计时掌握一个平衡点。

    如果我们确实需要对某些代码进行实质性的优化,那么首先要清楚哪一部分代码的执行最浪费时间。往往最浪费时间的代码很少,大部分是大量的循环最占用时间。

    优化的级别常分为三类:算法级优化;语言级优化;指令级优化。

    体现一个程序员水平最重要的地方就是算法。一个好的算法使用非常少的代码就能实现原来很复杂的操作,但这是很难做到的。尤其是这些算法经常与负载的善有关,所以需要比较和测试才能有好的效果。

    语言级优化就是采用较少的程序语言代码来代替冗长的代码块,例如,把某些赋值语句放到多循环的外面、使用inline函数、使用指针、用引用代替结构赋值、使用指针的移动代替内存拷贝、把初始化操作放在一开始而不是循环中间,等等。它所遵循的原则是“无代码”原则,减少需要执行的语句是提高速度的最直接的做法。这样的程序比较简捷,运行效果也比较稳定。

    指令级优化则要深入得多,这里所用的语言一般是汇编语言。这种方法的调试和测试比较复杂,程序不太容易懂,也更容易出错,结果有时与硬件有关。这种方法所针对的代码数量应该比较少,仅是关键的部分。这样的优化是以指令周期作为单位的。

    优化的内容一般有:

    代码替换

    使用周期短的指令代替周期长的指令。如,使用左移指令代替乘数是2的倍数的乘法;使用倒数指令代替除法指令。

    减少分支预测

    这是Pentium以上CPU特有的功能,它会执行该指令前预读一些指令,但是如果有分支就会造成预读的失效。

    并行指令

    这是Pentium以上CPU特有的多流水线的优势,两条(或多条,在Pentium  Pro以上)参数无关的指令可以被并行执行。

    MMX指令

    在处理大量字节型数据时可以用到它,一次可以处理8个字节的数据。

    指令的预读

    在读取大量数据时,如果该数据不在缓存里,将会浪费很多时间,因此需要提前把数据放在缓存中。这个功能在Pentium II的下一代CPU中出现。

    优化原则有以下3条:

    等价原则:经过优化后不应改变程序运行的功能。

    有效原则:使优化后所产生的目标代码运行时间确实较短,占用的空间确实较小。

    合算原则:应尽可能以较低的代价取得较好的优化效果,应当为值得优化的程序进行优化。

     

    展开全文
  • 程序优化

    万次阅读 2016-01-17 18:29:48
    一、 序言 程序优化是指利用软件开发工具对程序进行调整和改进,让程序充分利用资源,提高运行效率,缩减...在同一个处理器上,经过速度优化程序比未经优化程序在完成指定任务时需的时间更短,即前者比后者具

    一、 序言

    程序优化是指利用软件开发工具对程序进行调整和改进,让程序充分利用资源,提高运行效率,缩减代码尺寸的过程。按照优化的侧重点不同,程序优化可分为运行速度优化和代码尺寸优化。

    运行速度优化是指在充分掌握软硬件特性的基础上,通过应用程序结构调整等手段来降低完成指定任务所需执行的指令数。在同一个处理器上,经过速度优化的程序比未经优化的程序在完成指定任务时所需的时间更短,即前者比后者具有更高的运行效率。代码尺寸优化是指,采取措施使应用程序在能够正确完成所需功能的前提下,尽可能减少程序的代码量。

    然而,在实际的程序设计过程中,程序优化的两个目标(运行速度和代码大小)通常是互相矛盾的。为了提高程序运行效率,往往要以牺牲存储空间、增加代码量为代价,例如程序设计中经常使用的以查表代替计算、循环展开等方法就容易导致程序代码量增加。而为了减少程序代码量、压缩存储器空间,可能又要以降低程序运行效率为代价。因此,在对程序实施优化之前, 应先根据实际需求确定相应的策略。在处理器资源紧张的情况下,应着重考虑运行速度优化;而在存储器资源使用受限的情况下,则应优先考虑代码尺寸的优化。在下面的讨论中,主要以速度优化为主,同时对空间进行一些讨论。

    二、 优化原则

    在决定优化前,需要问自己的几个问题:为什么要优化、哪些程序需要优化、优化的目标是什么、能够接受由此带来的可能的资源消耗(人力、维护、空间等)吗?

    1. 如果说程序足够快,足以满足应用的需要,那还有优化的必要性吗?我认为没有必要。因为优化是一种时间与空间及代码可读性的权衡,可能带有一定的负作用,比如增加维护成本等。优化的目的是在现有程序不能满足应用的要求情况下,更加充分合理的利用现有资源去改进程序的运行效率。

    2. 确定程序过程中最花费时间的地方,决定哪些程序需要优化。在进行任何优化之前,必须找到程序的瓶颈所在。按80对20的一般性原则,在程序运行过程中,80%的时间消耗在20%的程序代码中。更有甚者,比如在很多图像处理中,95%的时间消耗在内层循环中的图像数据运算;而在另一些程序中,95%的时间消耗在读写数据文件上,而在数据的处理时间却少于5%。所以,在优化过程中,首先照顾那些最常用且最消耗时间的程序,才能起到显而易见的效果。优化不常用的代码不但浪费时间,而且使得代码不清晰,难于调试。

    3. 设计优化目标。程序优化是一门平衡的艺术,速度的优化往往要以牺牲程序的可读性或者增加代码长度及空间为代价,所以一定要设计合理的优化目标,将程序优化带来的资源消耗控制在合理的范围内。

    在清楚了以上问题之后,可以考虑从以下三个层级进行程序优化,尝试从不同的层级思考优化方案,它们依次产生更显著的优化代码:

    1. 算法优化

    程序的目的是通过计算机语言来解决现实存在的问题。在程序设计过程中,首先面对现实问题,然后提出解决问题的办法,设计相应的数据结构和算法。所以,在设计时要仔细考虑是选择链表还是数组,采用线性查找还是二分查找。尝试打破一些传统规则,发掘和怀疑自己的某些假定,恢复问题的本来面目,选择和构造适合于问题的算法。

    2. C/C++优化

    C/C++语言优化主要根据其运行机制,使用合理的实现方式,避免不必要的时间消耗。此层次不触及算法,面向的是程序代码,而不是问题本身,所以是以一种局部的思维方式去优化程序。改变变量存储位置、参数传递优化、对像构造方式、除法优化等都属于这一级,这个级别的优化需要对C/C++的运行机制有很好理解以及掌握大量小的优化技巧和知识,需要不断的学习和积累。

    3. 汇编优化

    汇编语言最接近机器语言,每条汇编指令对应一条机器指令。在汇编语言中可以直接操作寄存器,调整指令执行顺序。由于汇编语言直接面对硬件平台,而不同的硬件平台的指令集及指令周期均有较大差异。所以针对不同平台,可以选择寄存器优化、指令调整、循环展开等优化方法。

    在上面三个层次中,有时候层次之间的界限并不明显,在哪一个层次产生更显著的优化效果,需要依据具体的问题来分析。但有一点是确定的,在优化时一定要按上面的顺序来依次考虑问题,首先考虑在算法层面进行优化,如果在算法层次没有优化的空间,再考虑在C/C++层次的优化,最后才是依据硬件平台进行汇编优化。

    最后,给出优化过程中必须遵循以下原则:

    1. 程序必须正确,然后才有优化;

    2. 优化是有方向和侧重点的,不只是单纯的速度优化,它是时间与空间的平衡;

    3. 优化算法设计优先。充分优化的笨拙算法实现始终比不上一个更好算法的普通实现;

    4. 优化不只是实践,它是一种理论与实践的结合。优化需要掌握很多软件和硬件平台优化的技巧,但是更重的是C/C++运行机制的理解及对数据结构和算法的把握;

    5. 代码的优化是永无止境的,要避免过犹不及;

    6. 程序优化不是在软件编程结束后才进行,而是融入在程序编写过程中;

    7. 如果确信程序不需要优化,那就根本不进行优化;

    8. 清晰简洁的代码,很多时候就是最好的优化;

    三、 算法优化

    优化的第一步是构造适合问题的最佳算法。在程序设计过程,我们常常使用经典算法和传统模式去解决问题,一般情况下都保证程序的正确性。而很多经典算法都对问题作了一些假设,在面对实际问题时,为了获得更高的程序效率,我们需要重新检视这些假设,并尝试不同的角度思考问题,寻求有针对性于问题的新算法。

    算法设计中应该熟悉算法,知道各种算法的优缺点,针对不同的问题选择不同的算法。在某些情况下,需要对多种不同的算法进行测试,从中找到最适合问题的算法。将比较慢的顺序查找法用较快的二分查找法代替,插入排序或冒泡排序法用快速排序、堆排序代替,都可以大大提高程序执行的效率。选择一种合适的数据结构也很重要,比如你在一堆随机存放的数中使用了大量的插入和删除操作,那使用链表要快得多。如果需要较多随机存取表中元素,则最好采用线性表作为数据存储结构。

    另外,不要对一个简单的问题,使用复杂的算法。比如说,如果使用二分查找,甚至线性查找也是足够快,就没有必要使用Hash表来处理小数据量表,即“杀鸡焉用牛刀”。

    四、 C/C++优化

    此层次优化是以局部的思维方式看程序,不触及算法层级,它面向的是代码,而不是问题。语句调整,循环优化、参数传递优化等都属于这一级,这个级别的优化需要对C/C++的运行机制有很好理解以及掌握大量小的优化技巧和知识,需要不断的积累。与此层次对应的是编译器,即当前C语言将编译成哪些汇编指令。如今,多数编译器都支持对程序速度和程序大小的优化,有些编译器还允许用户选择可供优化的内容及优化的程度。简单的语句调整、公共表达式提取、废代码删除等,当前的很多编译器也能做到了。然而,大部分编译器必须是保守的,除非用户指定了采用特定的优化方式,否则编译器只能依据最可靠的方法将C/C++语言,翻译机器语言,所以需要了解一些编译器的优化能力,以使自己的代码配合编译器做好优化。

    以下文中,所有生成汇编代码,均是由GUN ARM编译器生成,优化级别为O3(生成汇编方法,可以参看附录)。

    1. C/C++变量优化

    C/C++语言中变量依据其定义的方式不同,其存放位置可以分为寄存器、栈区、堆区和静态存储区三种区域。

    (1)寄存器上分配。当函数中定义的局部变量不多,且没有对局部变量的取地址操作时,则会将该变量会分配在寄存器中。当进行运算时,直接读寄存器,速度非常快。

    (2)在栈上分配。用户在函数体中定义了较多局部变量后,或对变量进行取地址操作,通过结构体返回值,则相应的变量会放在栈空间。函数执行结束后,这些存储单元自动被释放。一般的情况,由于栈区中数据在函数中都会被重复用到,加载时都能够Cache命中,一个周期内完成,效率很高,但是其分配的内存容量有限。

    (3)从静态存储区域分配。在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量、static变量。在ARM9平台,从静态区加载数据到寄存器,一般需要3个周期。如果在循环中,无序访问数据造成Cache不能命中,那么每次都需要3个周期加载,则比较费时。所以尽量让数据顺序访问,提高Cache命中率和访问速度。

    (4)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请的内存,程序员自己负责用free或delete释放内存,其访问速度和静态区相同。

    clip_image001

    建议1:把重复使用的指针型参数拷贝到本地局部变量。

    参看下面的代码:

    clip_image002

    比较左边的C代码,差别在于将step指向的值赋给局部变量temp。程序员一般会认为,A框中列出的 C代码,节约了一个整形变量的存储空间,而且直接用指针进行运算,少了一条赋值语句,速度应该更快。其实不然,将两部分C代码汇编后,再比较右边汇编指令,并没有节约存储空间(原因是变量分配在寄存器中),而且b中汇编代码到在计算每二个表达式时,少用了一条LDR指令。为什么呢?因为编译器不能断言step指针,是否和t1指向同一个地址,即在计算第一个表达式后,其指向值是否发生了改变。为了保证程序正确性,在第二次引用时,只能再次从内存加载step,增加了一个LDR指令周期数。对于这种指针型参数,编译器不能做到优化,如果step处于循环中,情况则更加糟糕。所以只能由程序员自己控制,配合编译器做好优化工作。

    建议2:尽量选用32位的数据类型变量。

    ARM 指令集支持有符号/无符号的8 位、16 位、32位整型变量。恰当的使用变量类型,不仅可以节省目标代码指令,并且可以提高代码运行效率。在程序编写过程中,应该尽可能地避免使用char、short 型的局部变量,因为操作8位/16位局部变量往往比操作32位变量需要更多指令, 请对比下列函数和它们的汇编代码。

    clip_image003

    首先比较左边的C代码,唯一的差别是计数器变量i的定义不同。一般认为,采用short类型变量比int节约了两个字节。其实并没有,因为ARM是32位运算(除非处于Thumb模式),寄存器也是32位,并不存在节约了两个字节。在i定义为32整型时,在对应的汇编代码中可以说编译器已经做到极致优化,其将循环结束条件都进行了优化。而在计数器定义为16位整形时,编译器则不但没有优化循环条件,而且还将计数器每次加1之后,利用移位操作对其进行宽度调整,将32位整形其转化为16位整型,增加了两条MOV指令。所以,在变量定义中,尽量使用32位宽度的数据类型,小于32位宽度的变量不但没有节约内存,还增加了很多无用的指令操作。在定义局部变量时,选用32位的数据有利于编译器优化。

    建议3:尽量在需要使用一个对象时才声明,并用初始化的方法赋初值。

    只要你定义了一个变量而且其类型带有一个构造函数或析构函数,那么当程序运行到这个变量定义式时,你就得承担构造成本;当这个变量离开作用域时,你得承担析构成本。即使这个变量最终没有被使用,仍需要耗费这些成本,所以要尽量避免这种情形。

    看如下面代码,定义一个CString类,其中有构造函数,析构函数及拷贝构造和赋值函数和一个获得字符串长度的函数。

    clip_image004

    下面利用CString定义如下两段基本相同的代码,只是局部变量str定义的位置不同。

    clip_image005

    对两种情况,进行一次调用函数对比(除去GetLength操作),如下。

    代码A:pStr->GetLength()==0时,1个构造函数 + 1个析构函数;

    pStr->GetLength()!=0时,1个构造函数 + 1个析构函数 + 1个赋值函数;

    代码B:pStr->GetLength()==0时,没有函数调用;

    pStr->GetLength()!=0时,1个拷贝构造函数 + 1个析构函数;

    可以看出对于A代码,无论什么情况,至少有1次构造和析构函数调用。最好情况,也比B情况下,多一次赋值函数调用。所以尽可能的滞后局部变量定义,最好延期到能够给它赋初值为止,可以提高程序速度。

    下面再看看循环体中局部变量定义,请看如下代码:

    clip_image006

    对循环体两种情况,进行一次调用函数对比,如下。

    代码A: 1个构造函数 + 1个析构函数 + n个赋值函数;

    代码B: n个拷贝构造函数 + n个析构函数;

    对于这两种情况就,需要考虑每个函数调用花费是多大了,然后再决定采用哪种写法。

    对于基本数据类型…(未完)

    2. 参数传递优化

    ARM在函数调用时,如果参数少于四个,则通过R0-R3传递参数。如果多于四个参数,则会将参数从右向左的顺序入栈。所以,在函数设计时,尽量限制函数的参数,不要超过4个,这样可以省去参数入栈操作,提高函数调用效率。

    另外,在函数调用过程中,如果通过传值的形式传递结构体参数和返回值时,则可能造成拷贝构造函数的调用。对于此类传递,尽可能选用指针或引用传递,不会增加额外的函数调用负担。看下面的函数中参数的传递方式。

    C.常量传递

    A.传值方式

    void fun_2(CString *pStr)

    {

    ...

    return;

    }

    B.指针传递

    void fun_1(CString pStr)

    {

    ...

    return;

    }

    clip_image007

    void fun_3(const CString *pStr)

    {

    ...

    return;

    }

    函数func_1以传值的方式传递参数,必然会调用CString的拷贝构造函数和析构函数,函数运行过程中不会改变调用者的内容。

    函数func_2以指针的方式传递参数,不会调用CString的拷贝构造函数和析构函数,但函数运行过程中,可以对调用者参数所指向的内容进行改变,会造成不安全性。

    为了保证调用者的值不会被改变,同时也不调用拷贝函数,那么就可以func_3方式传递参数,在类型前加上const关键字。

    3. 合理使用内联函数

    在编译内联函数时,编译器首先对参数和返回值进行检查,确认正确后,内联函数的代码就会直接替换函数调用。这样,就可以省去函数调用的开销,提高函数的执行效率。但是,每一内联函数的调用处都会有其一份拷贝,无疑增大了代码体积,程序加载后消耗更多的内存空间。所以,内联函数是典型的“以空间换时间”的优化策略。如果函数体内代码的执行时间远大于函数调用开销,那么内联的意义就不大了。

    所以,使用内联函数时注意以下事项:

    (1)将大多数内联函数限制在小型、被频繁调用的函数身上。

    (2)内联会导致目标代码体积变大,在空间有限的情况下,不宜使用内联函数。

    现在的编译器,都会拒绝将过于复杂的函数内联,自动地取消不值得内联的函数。

    4. 分支优化

    条件分支(if语句、switch语句)是编程中经常使用的基本操作,然而在某些时候(如循环)它可能带来严重的性能问题。在ARM上,分支是通过跳转指令B来实现。在ARM7和ARM9中,跳转指令B需要3个指令周期数,是占用指令周期数比较长的一条指令(原因是会清空流水线)。所以,如果在循环中大量使用条件分支,则严重影响程序效率。在XScale中,ARM处理器能对条件分支做出预测。如果分支预测正确,那么跳转指令B只需要花费1个指令周期,而如果预测错误,那么需要增加4个指令周期。这就是分支预测错误的惩罚。

    下面将讨论条件分支的一些有效优化方法。

    1.把条件分支移动到循环外

    先看如下代码,

    clip_image008

    对于A代码中奇偶模式的随机分支,即使当前最好的CPU也不可能做出好的预测,对于这种情况就要改写成两个for循环,一个处理偶数,一个处理奇数,如代码B。

    2.单独处理条件分支。

    图像处理算法,经常需要判断边界像素点,进行特殊处理;可以考虑的优化方案是把边界区域和内部区域分开处理;或者条件允许的话,扩大原图像的边界,形成"哨兵"数据,这样访问像素的时候就不用考虑越界的问题了。

    3.合并多个条件来减少条件分支

    编写代码过程中,常常使用 if((pStr1==0)&&( pStr2==0)&&( pStr3==0)),编译器将生成3个条件跳转指令,而且使分支可预测性大大降低,可以改写为: if((pStr1|pStr2|pStr3)==0) 从而同时改进代码和分支预测准确率。经测试,在GUN ARM编译器的O1优化级别上,能够对于上述语句自动优化。对下面的语句,编译器不能做到优化(因为编译器不能断定b0,b1,b2>=0),会产生三个比较指令和三个跳转指令,这种情形就需要程序员自己优化。

    if((b0>=64)||(b1>=64)||(b2>=64))  //b0,b1,b2>=0

    改写为:if((b0|b1|b2)>=64)

    5. 循环优化

    在ARM中的循环结构中,循环的终止条件将影响其执行效率,因为ARM指令有条件执行特性,所以在书写循环时尽量以0为终止条件。这样编译器可以用一条BNE(若非零则跳转)指令代替CMP (比较)和BLE (若小于则跳转)两条指令,既减小代码尺寸,又加快了运行速度。

    比较下面代码,其中R0=i,以10为终止条件比以0为终止条件的循环多了一条比较指令。

    clip_image009

    建议4:在循环书写过程中,如果循环体内代码与计数器i的增减顺序无关,尽量采用0作为循环终止条件。

    另外,一些不需要在循环中参加运算的任务放到循环外面,包括表达式、函数的调用、指针运算、数组访问等。

    6. 乘/除法优化

    在ARM中没有除法指令,所有的除法操作都是调用C库函数实现除法运算,模运算(%)也是通过除法运算来实现,一般要花费20~100个周期,是最慢的基本算术运算。

    建议5:在编程过程中,尽量避免除法。

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

    有些除法可用乘法代替,例如: if ( (x / y) > z)可变通为 if ( x > (y * z)) 。

    在能满足精度,且存储器空间冗余的情况下,也可考虑使用查表法代替除法。

    当除数为2的幂次方时,用移位操作代替除法。现在大部分编译器都能做到移位优化。如下所示:

    a=a/8,(当a>0)改为:a=a>>3,(当a<0)改为:a=(a+1)>>3;

    a=a%8,(当a>0)改为:a=a&7;

    另外,乘以任何一个整数都可以用移位和加法来代替乘法。ARM中加法和移位可以通过一条指令来完成,且执行时间少于乘法指令。例如: i = i * 5(3个指令周期) 可以用i = (i<<2) + i (1个指令周期)来代替。

    7. 浮点优化

    大多数ARM处理器硬件并不支持浮点运算。这就意味着编译器要把每一个浮点运算转换成一个子程序调用,用整型运算来模拟浮点运算。在使用浮点时,可以对其进行转化整形运算,将提高运行速度。如,图像缩放过程中,需要计算原图像和目标图像的比值,那么就有小数位,如果采用浮点数运算,则会影响程序速度。那么可以用移位操作放大被除数,将浮点数转化为整数来表示,看下面代码。

    clip_image010

    对于上面的将浮点转化为整型运算后,一般的情况下,程序的运行速度要快2~3倍。在转化过程中,要注意防止移位溢出。比如示例4-7的代码中,要求原图像的宽度和高度都必须小于65536,否则左移16位会造成溢出。

    五、 汇编优化

    汇编优化相对应的是具体硬件平台。用汇编重写并不是简单把高级语言改写为汇编实现,那样的汇编很可能没有当今编译器产生的代码好,所以如果决定用汇编实现,那就应该按照汇编的角度来规划自己的实现,适当的参考编译器生成的汇编码是可取的(特别是新手)。另外,在某些领域,使用处理器的新特性和新的指令集(如MMX) 等,将产生巨大的性能收益,这些地方经常采用汇编来实现。

    1. 流水线

    为了加快CPU的处理频率,现代CPU都设计了多级流水线,ARM处理器也不例外。ARM7采用3级流水线,ARM9采用5级流水线。下面以ARM9简单说明。

    clip_image011

    l 取指 在PC地址处取出指令。指令被加载到内核,然后进入流水线。

    l 译码 对前一个周期中取到的指令进行译码。

    l 运算 执行译码操作所得指令。

    l 加载1 通过LDR指令从内存加载特定数据。如果不是加载指令,那么这个步骤无效。

    l 加载2 通过LDRB、LDRH加载字节或半字数据。如果不是加载指令,那么这个步骤无效。

    2. 流水线阻塞

    如果在运算中一条指令需要前一条指令的执行结果,而这时结果还没有出来,那么处理器就会等待,这称为流水线数据相关。

    如果在运算中,一条指令运算需要前一条指令的运算部件,而前一条指令运算没有结束,那么处理器会等待,这称为流水线部件相关。典型是乘法部件。

    clip_image012

    在上面的汇编代码A中,LDR指令需要2个指令周期,其后紧跟的ADD指令需要使用LDR的结果,这时数据还未加载成功,相应产生了数据相关。那么,处理器只能等待加载成功,然后再进行ADD指令,流水线产生了1个指令周期的间隙。

    在汇编代码B中,第一条MUL指令在运算中占用了乘法部件,需要2个指令周期才能结束运算。其后的第二条指条也需要使用乘法部件,造成了部件相关,处理器等待第一个乘法结束后再进行第二个乘法,也产生了1个指令周期的间隙。

    3. 流水线优化

    由前面叙述可知,ARM指令执行是在指令流水线中进行,一条指令执行的时间会受其相邻指令的影响。流水线延迟或阻断会对处理器的性能造成影响,因此应该尽量保持流水线畅通。优化过程中可以通过调整代码中的指令序列,在指令间隙之间插入不存在相关性的指令,以使流水线满负荷运转。另外,跳转指令B将清空流水线重新加载指令,也严重影响流水线畅通。所以,要尽可能避免。对上面产生相关的指令序列进行调整如下。将序列中不相关的CMP指令插入到相关指令之间,填满流水线,去掉了一个指令周期的间隙,提高了流水线的吞吐率。

    clip_image013

    4. 寄存器分配优化

    CPU 对寄存器的存取要比对内存的存取快得多, 因此为变量分配一个寄存器,将有助于代码的优化和运行效率的提高。整型、指针等类型的变量都可以分配寄存器;一个结构的部分或者全部也可以分配寄存器。给循环体中需要频繁访问的变量分配寄存器也能在一定程度上提高程序效率。寄存器优化的基本原则是最大限度的使用寄存器,最少次数访问存储器。

    5. 合理使用条件指令

    ARM指令集的一个重要特征就是大多数的指令均可包含一个可选的条件码。当程序状态寄存器CPSR中的条件标志满足指令条件时,带条件码的指令才会执行。默认情况下,ARM指令并不会更新ARM寄存器CPSR中的N,Z,C,V标志。对大多数的指令,若要更新这些标志,则需要对指令助记符加后缀S。例外的是不写入目标寄存器的CMP指令,直接更新CPSR的标志位,因此不需要S后缀。利用条件执行通常可以省去单独的判断指令,因而可以减小代码尺寸并提高程序效率。

    例1:

    下面以一个if分支判断为例说明,

    clip_image014

    R0 = a, R1 = b使用条件指令的代码,相对未用条件代码,少了一条CMP指令。

    例2:

    clip_image015

    其中:R1 = a, R2 = x, R3 = y。

    在未使用条件指令的情况下,如果分支L1,则需要5个周期数,如果分支L2,则需要8个周期数。如果分支的情况各占50%,则未用条件指令平均需要(5*0.5+8*0.5)=6.5周期数。而使用条件指令,则恒定需要3个周期数。平均少用了3.5个周期。

    注意:

    对于示例5-5中,如果分支中所需要较多的操作,则不适合选用条件指令。因为,条件指令无论是否执行,都至少占用一个周期。

    6. 循环展开:

    流水线阻断的情况可通过循环拆解等方法加以改善。一个循环可以考虑拆解以减小跳转指令在循环过程中所占的比重,进而提高代码效率。以while循环为例生成的汇编指令

    clip_image016

    其中,R0 = i。

    这个循环开销包括一条设置条件标志的减法指令SUBS和跳转指令BGT.在ARM9平台上,减法指令SUBS占用1个周期数,跳转指令B占用3个周期数,也就是说循环一次的最小成本为4个指令周期数。如果在do something所占用4个指令周期,则有效指令周期数和逻辑控制指令周期数各占用50%。如果循环体内代码,一次处理四个对像(如3中代码),则有效指令周期数占一次循环指令总周期数的比重为80%,提高的代码执效率。

    注意:

    1. 对于较简单循环体函数,有很好的效果,如果循环体很大,则循环展开的意义不大。

    2. 循环展开后,程序的代码量将会增大,所以空间有限的情况下,要在时间与空间上权衡,避免过度展开。

    3. 循环展开后,要合理的处理计数器,避免访问越界。计数器最好以0为终点,这样可以省去与终点的比较指令。

    7. 有效使用地址模式

    LDR/STR指令中的自动索引(auto-indexing)功能时可以完成在加载过程中,往基址寄存器上加一个偏移量的操作,供后面的指令使用。

    例如:指令LDR R1, [R2], #4 完成R1= (*R2)及 R2 += 4两个操作,是后索引(post-indexing)的例子;而指令 LDR R1, [R2, #4]! 完成 R1 = *(R2 + 4) 和 R2 +=4 两个操作,是前索引(pre-indexing)的例子。上述指令将加载和地址变化合二为一,减少了一条指令,特别适合于数组处理中。

    8. SIMD单指令多数据

    在不带MMX指令的ARM处理器(ARM7和ARM9TDMI等)上,一般是不支持SIMD单指令多数据处理。但是合理的安排数据,还是可以进行并行操作的。比如两幅图像按alpha(0--255)合成中,合成公式为

    dst = (src1 * alpha + src2 * (255- alpha) ) / 256 (式1)

    = ((src1–src2) * alpha + src2*255) / 256。 (式2)

    图像中每Pixel中的A、R、G、B四个通道各占8位,对式2中,如果按A、G和R、B分解,用一个32位寄存器来保存,然后进行减法、乘法运算和移位操作,那么就可每次进行两个通道的并行计算。参看下面的代码和运算过程:

    clip_image017

    在计算R、B之后,再计算A、G两个通道,最后再将两次计算的结果进行合并,完成一个像素的混合。在上述多路并行运算中也要注意防止溢出,此示例中由于alpha的值处于0到255之间,所以不会溢出。

    9. MMX优化

    ...(未写)


    附录:

    1. 在Elastos 的GNU ARM环境使用GCC产生汇编代码的方法

    命令:gcc –O3 –S test.c

    参数说明:

    –On表示编译器对程序的优化级别,共有O1、O2、O3三个级别。如果不带此参数说明,则不进行编译器优化;

    –S 表示生成.S汇编文件;

    test.c 表示需要生汇编的C代码;

    2. GNU ARM 汇编和armasm宏汇编的格式差别

    每种编译器对其汇编指令格式都有不同的规定,下面主要区别GUN ARM和armasm宏汇编的区别。

    GNU Arm编译器对于函数调用采用以global开头,后面给出一个加下划线的外部联结标号,标号通过后面有一个冒号来识别。注释采用”@”符号。

    .global _ARGB32Blit2ARGB32

    _ARGB32Blit2ARGB32:

    STMFD SP!, {R4-R12,LR} @注释

    ...

    LDMFD SP, {R4-R12, PC}

    Armasm编译器对要求在任何ARM指令或Thumb指令出现以前,用一个AREA保留字定义一个范围。所有汇编文件必须用END保留字表示结束。标号的定义必须在一行的顶格书写。注释采用”;”符号。

    AREA sample, CODE, READONLY

    EXPORT ARGB32Blit2ARGB32

    ARGB32Blit2ARGB32

    STMFD SP!, {R4-R12,LR} ;注释

    ...

    展开全文
  • Python代码优化优化原则

    千次阅读 2015-02-13 17:02:58
    本文介绍了进行Python代码优化需要遵循的基本原则,该原则也适用于其它语言的代码优化工作。 优化原则 不论最终的优化结果如何,代码优化工作总是有代价的。如果代码能够正常工作,却花了大量精力是其运行的更快,...

    简介

    本文介绍了进行Python代码优化需要遵循的基本原则,该原则也适用于其它语言的代码优化工作。

    优化原则

    不论最终的优化结果如何,代码优化工作总是有代价的。如果代码能够正常工作,却花了大量精力是其运行的更快,未必是一件值得尝试的有价值的事情。

    进行代码优化,需要记住几条原则:

    1) 确保代码能够正常工作之前不要考虑优化工作

    应该避免在编写代码的同时,对其进行优化,这是程序员最常见的错误。因为实际情况是,对于复杂的系统,在你编写代码的时候,你并不能得到系统运行的完整视图,因此也无法确定当前的代码是否会成为系统性能的瓶颈。

    但这并不意味着程序员不需要尝试编写运行更快的代码。这里强调的是首先保证程序能够正常的工作。在使代码能够正常工作并且进行代码的剖析之前,不要做代码优化工作,如用C/C++语言来对代码的一个部分进行扩展。

    二八原则:1897年,意大利经济学家帕列托在对19世纪英国社会各阶层的财富和收益统计分析时发现:80%的社会财富集中在20%的人手里,而80%的人只拥有社会财富的20%,这就是“二八法则”。“二八法则”反应了一种不平衡性,但它却在社会、经济及生活中无处不在。在任何一组东西中,最重要的只占其中一小部分,约20%,其余80%尽管是多数,却是次要的,因此又称二八定律。

    对于代码优化工作,帕列托定律同样适用。影响性能的代码往往集中在少部分代码,过早的或不必要的性能优化,往往是项目和程序员的噩梦。

    用Kent Beck的话来作为本小结的小结:

    Optimization is carried out on programs that already work. 

    “Make it work, then make it right, then make it fast.” 

    2)  做必要的优化

    优化工作是有成本的,开发团队需要对优化进行仔细的评估并认真地安排优化的优先级。做相关工作时,不妨问问自己下列的问题:

    • 优化的要求是否来自真正的客户(对开发框架或程序库,开发人员也是客户)
    • 程序真的运行很慢,还是处于可以接受的范围;是否有相关的度量数据,还是用户的直观体验
    • 提升运行速度的成本(时间成本,财务成本,对现有版本稳定性的影响等)是多少?是否值得做?
    • 具体程序的哪部分需要优化?

    3) 代码优化不应该牺牲代码的可读性和可维护性

    优化工作可能使代码变得混乱,并且牺牲代码的可读性。如果确实发生这种情况,首先评估当前优化是否已经满足性能的要求;如果确实需要进一步优化,需要考虑替代的方案,如扩展或重新设计。总之, 需要在生成易读且易维护的代码和运行速度之间找到一个平衡点。

    参考资料

    1. Expert Python Programming

     

    展开全文
  • 这篇文章讲解的是关于编程应该遵循原则。 我只是大致翻译一下,还是细看英文吧,这样才不会被别人误导。 The Principles of Good Programming by Christopher Diggins Today'...
  • 代码优化原则与方法优化,如何写出好的程序,适合讲解优化的培训。
  • java代码优化六大原则

    万次阅读 2017-05-29 20:35:29
    单一职责代码优化第一步,单一职责原则 (Single Responsibility Principle)。对于一个java类,应该仅有一个引起它变化的原因,也就是说,一个类中,应该是一组相关性很高的函数、数据的封装。但是这个原则的界限...
  • 数据库优化原则

    千次阅读 多人点赞 2014-12-30 19:49:13
    本文总结了一些数据库的优化规则和设计知识,整理出来20条数据库优化的实践原则···
  • 嵌入式程序优化

    千次阅读 2012-03-04 19:27:20
    嵌入式系统由于受功耗、成本和体积等因素的制约,嵌入式微处理器的处理能力与桌面系统处理器相比也存在较大差距,故... 嵌入式应用程序优化,指在不改变程序功能的情况下,通过修改原来程序的算法、结构,并利用软
  • 程序性能优化

    千次阅读 2020-08-16 16:11:17
    体验评分是一项给小程序的体验好坏打分的功能,它会在小程序运行过程中实时检查,分析出一些可能导致体验不好的地方,并且定位出哪里有问题,以及给出一些优化建议。 使用流程: 1、打开开发者工具,在详情里切换...
  • 性能优化原则优化模式

    千次阅读 2018-10-02 12:08:32
    一般而言,性能优化指降低响应时间和提高系统吞吐量两个方面,但在流量高峰时候,性能问题往往会表现为服务可用性下降,所以性能优化也可以包括提高服务可用性。在某些情况下,降低响应时间、提高系统吞吐量和提高...
  • 1. 使用小程序时,是否会经常遇到如下问题? 打开是一直白屏 打开是loading态,转好几圈 我的页面点了怎么跳转这么慢? 我的列表怎么越滑越卡? 2. 我们优化的方向有哪些? 启动加载性能 渲染性能 3....
  • WPF程序性能优化总结

    千次阅读 2017-08-25 13:11:11
    WPF程序性能由很多因素造成,以下是简单地总结: 元素: 1、 减少需要显示的元素数量:去除不需要或者冗余的XAML元素代码. 通过移出不必要的元素,合并layout panels,简化templates来减少可视化树的层次。这可以...
  • 一些优化ABAP程序性能的方法

    千次阅读 2009-08-03 22:08:00
    但是在程序初期,由于没有大量的测试数据,我们很难发现一些程序的性能瓶颈在哪里,更无从谈如何优化性能了。不过,我想,如果在开发早期遵循一些好的开发方法,就有可能避免后期程序发生大的性能问题。影响 ABAP ...
  • 【微信小程序】性能优化

    千次阅读 2018-07-14 04:00:20
    为什么要做性能优化? 一切性能优化都是为了体验优化 1. 使用小程序时,是否会经常遇到如下问题? 打开是一直白屏 打开是loading态,转好几圈 我的页面点了怎么跳转这么慢? 我的列表怎么越滑越卡? 2. ...
  • 优化程序性能的几点: 1)高级设计。选择恰当的数据结构和算法;...所以请遵循基本编码原则。 2,程序性能的表示:每元素的周期数(CPE) 3,实际的处理器中,是同时执行多条指令的(因为一条指令有多
  • 浅析嵌入式程序设计中的优化问题

    千次阅读 2009-10-21 02:48:00
    嵌入式系统由于受功耗、成本和体积...1 嵌入式程序优化的类型嵌入式应用程序优化,指在不改变程序功能的情况下,通过修改原来程序的算法、结构,并利用软件开发工具对程序进行改进,使修改后的程序运行速度更高或代码尺
  • 让你提前认识软件开发(16):如何对程序进行优化

    千次阅读 多人点赞 2014-04-26 19:48:11
    如何对程序进行优化?    对程序进行优化,是软件开发工程师必然会涉及到的问题。那么为什么要对程序进行优化呢?原因有以下几个:  第一,在原程序基础之上新增、删除或修改了功能,需要改变原程序流程。客户...
  • 制定目标时需要遵循的SMART原则

    万次阅读 2016-02-13 20:13:56
    所谓目标管理乃是一种程序或过程,它使组织中的上级和下级一起协商,根据组织的使命确定一定时期内组织的总目标,由此决定上、下级的责任和分目标,并把这些目标作为组织经营、评估和奖励每个单位和个人贡献的标准。...
  • 3.2 类计的基本原则 类是属性和方法(行为)的容器,但它不是垃圾桶,更不能是四像八不像。 类是对技术领域和业务领域客观实体(可能是虚拟实体)的抽象和表达,必须反映其真实的本质的属性和行为,杜绝人为...
  • C++应用程序性能优化读书笔记

    千次阅读 2009-07-03 20:00:00
     第一篇 C++程序优化基础第1章 C++对象模型1.1 基本概念1.1.1 程序使用内存区 一个程序占用的内存区一般分为5种:全局/静态数据区、常量区、代码区、栈、堆。 例子代码: #include #include int nGlobal = 100;
  • 作者:pengdai出处:https://www.cnblogs.com/pengdai一、开发原则S:单一职责SRPO:开放封闭原则OCPL:里氏替换原则LSPI:接口...
  • 代码优化中的优化

    2007-05-10 09:41:00
    常见的代码优化项目:1. 输入、输出操作2. 通常的系统调用3. 未知的程序错误提高程序性能的若干方法:1. 修改程序需求2. 修改程序的设计3.... 性能问题遵循:80/20原则,需要找出性能的瓶颈出来7.
  • 稳定性是第一前提,如系统崩溃恢复容灾备份这些,主要是一些数据保护的机制,还有就是程序参数的校验、异常的处理、事务的回滚、程序边界的设计(合理的边界划分可以避免服务的连锁崩溃)、对账机制等,这些都是日常...
  • 索引的创建原则基于合理的数据库设计,经过深思熟虑后为表建立索引,是获得高性能数据库系统的基础。而未经合理分析便添加索引,则会降低系统的总体性能。索引虽然说提高了数据的访问速度,但同时也增加了插入、更新...
  • Lua性能优化技巧

    2015-05-25 12:40:06
    在Lua中,我们应遵循两条关于程序优化的箴言: 原则1:不要做优化 原则2:依然不要做优化(对专家而言)
  • unity3D的优化总结

    2013-03-28 17:21:23
    这是关于unity3d在开发中的优化注意事项,遵循这些原则可以是你的程序更加轻便
  • 介绍了应用程序设计优化、数据库优化、部署环境优化及其他优化优化建议。 对于在运行系统,遇到性能问题后我们应该遵循“先环境后应用”、“先数据库后程序”、“先易后难”的原则进行优化
  • Python性能优化

    千次阅读 2014-09-12 20:30:25
    python 作为脚本的一个不足之处,那就是执行效率和性能不够理想,特别是在 performance 较差的机器上,因此有必要进行一定的代码优化来提高python程序的执行效率。 Python 代码优化常见技巧 当你的程序运行地很慢的...
  • 良好的编程原则与良好的设计工程原则密切相关。本文总结的这些设计原则,帮助开发者更有效率的编写代码,并帮助成为一名优秀的程序员。 1.避免重复原则(DRY – Don’t repeat yourself) 编程的最基本原则是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 51,163
精华内容 20,465
关键字:

优化所遵循的原则是程序的