精华内容
下载资源
问答
  • 几个简单代码优化方法

    千次阅读 2018-01-05 22:05:29
    几个简单代码优化方法 提取代码 提取前 void renderBanner() { if ((platform.toUpperCase().indexOf("MAC") > -1) && (browser.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize > 0 ) {

    几个简单代码优化方法

    提取代码

    提取前

    void renderBanner() {
      if ((platform.toUpperCase().indexOf("MAC") > -1) &&
           (browser.toUpperCase().indexOf("IE") > -1) &&
            wasInitialized() && resize > 0 )
      {
        // do something
      }
    }

    提取后

    void renderBanner() {
      final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
      final boolean isIE = browser.toUpperCase().indexOf("IE") > -1;
      final boolean wasResized = resize > 0;
    
      if (isMacOs && isIE && wasInitialized() && wasResized) {
        // do something
      }
    }

    Inline Temp

    内联变量之前,减少代码量,方便查看。

    double hasDiscount(Order order) {
      double basePrice = order.basePrice();
      return (basePrice > 1000);
    }

    内联变量之后

    double hasDiscount(Order order) {
      return (order.basePrice() > 1000);
    }

    Replace Temp with Query

    用变量取代查询之前

    double calculateTotal() {
      double basePrice = quantity * itemPrice;
      if (basePrice > 1000) {
        return basePrice * 0.95;
      }
      else {
        return basePrice * 0.98;
      }
    }

    用变量之后,读起来更有目的性

    double calculateTotal() {
      if (basePrice() > 1000) {
        return basePrice() * 0.95;
      }
      else {
        return basePrice() * 0.98;
      }
    }
    double basePrice() {
      return quantity * itemPrice;
    }

    Substitute Algorithm

    用算法替代之前

    string FoundPerson(string[] people)
    {
      for (int i = 0; i < people.Length; i++) 
      {
        if (people[i].Equals("Don"))
        {
          return "Don";
        }
        if (people[i].Equals("John"))
        {
          return "John";
        }
        if (people[i].Equals("Kent"))
        {
          return "Kent";
        }
      }
      return String.Empty;
    }

    用算法替代之后

    string FoundPerson(string[] people)
    {
      List<string> candidates = new List<string>() {"Don", "John", "Kent"};
    
      for (int i = 0; i < people.Length; i++) 
      {
        if (candidates.Contains(people[i])) 
        {
          return people[i];
        }
      }
    
      return String.Empty;
    展开全文
  • C++代码优化方法总结

    千次阅读 2008-11-07 21:09:00
    C++代码优化方法总结 优化是一个非常大的主题,本文并不是去深入探讨性能分析理论,算法的效率,况且我也没有这个能力。我只是想把一些可以简单的应用到你的C++代码中的优化技术总结在这里,这样,当你遇到几种不同...
     
    

    C++代码优化方法总结

    优化是一个非常大的主题,本文并不是去深入探讨性能分析理论,算法的效率,况且我也没有这个能力。我只是想把一些可以简单的应用到你的C++代码中的优化技术总结在这里,这样,当你遇到几种不同的编程策略的时候,就可以对每种策略的性能进行一个大概的估计。这也是本文的目的之所在。
    一. 优化之前
    在进行优化之前,我们首先应该做的是发现我们代码的瓶颈(bottleneck)在哪里。然而当你做这件事情的时候切忌从一个debug-version进行推断,因为debug-version中包含了许多额外的代码。一个debug-version可执行体要比release-version大出40%。那些额外的代码都是用来支持调试的,比如说符号的查找。大多数实现都为debug-version和release-version提供了不同的operator new以及库函数。而且,一个release-version的执行体可能已经通过多种途径进行了优化,包括不必要的临时对象的消除,循环展开,把对象移入寄存器,内联等等。
    另外,我们要把调试和优化区分开来,它们是在完成不同的任务。 debug-version 是用来追捕bugs以及检查程序是否有逻辑上的问题。release-version则是用来做一些性能上的调整以及进行优化。
    下面就让我们来看看有哪些代码优化技术吧:

    二. 声明的放置
    程序中变量和对象的声明放在什么位置将会对性能产生显著影响。同样,对postfix和prefix运算符的选择也会影响性能。这一部分我们集中讨论四个问题:初始化v.s 赋值,在程序确实要使用的地方放置声明,构造函数的初始化列表,prefix v.s postfix运算符。
    (1) 请使用初始化而不是赋值
    在C语言中只允许在一个函数体的开头进行变量的声明,然而在C++中声明可以出现在程序的任何位置。这样做的目的是希望把对象的声明拖延到确实要使用它的时候再进行。这样做可以有两个好处:1. 确保了对象在它被使用前不会被程序的其他部分恶意修改。如果对象在开头就被声明然而却在20行以后才被使用的话,就不能做这样的保证。2. 使我们有机会通过用初始化取代赋值来达到性能的提升,从前声明只能放在开头,然而往往开始的时候我们还没有获得我们想要的值,因此初始化所带来的好处就无法被应用。但是现在我们可以在我们获得了想要的值的时候直接进行初始化,从而省去了一步。注意,或许对于基本类型来说,初始化和赋值之间可能不会有什么差异,但是对于用户定义的类型来说,二者就会带来显著的不同,因为赋值会多进行一次函数调用----operator =。因此当我们在赋值和初始化之间进行选择的话,初始化应该是我们的首选。
    (2) 把声明放在合适的位置上
    在一些场合,通过移动声明到合适的位置所带来的性能提升应该引起我们足够的重视。例如:
    bool is_C_Needed();
    void use()
    {
      C c1;
      if (is_C_Needed() == false)
      {
          return; //c1 was not needed
      }  
      //use c1 here
        return;
    }
    上面这段代码中对象c1即使在有可能不使用它的情况下也会被创建,这样我们就会为它付出不必要的花费,有可能你会说一个对象c1能浪费多少时间,但是如果是这种情况呢:C c1[1000];我想就不是说浪费就浪费了。但是我们可以通过移动声明c1的位置来改变这种情况:
    void use()
    {
     if (is_C_Needed() == false)
        {
          return; //c1 was not needed
        }  
        C c1; //moved from the block's beginning
      //use c1 here
      return;
    }
    怎么样,程序的性能是不是已经得到很大的改善了呢?因此请仔细分析你的代码,把声明放在合适的位置上,它所带来的好处是你难以想象的。
    (3) 初始化列表
        我们都知道,初始化列表一般是用来初始化const或者reference数据成员。但是由于他自身的性质,我们可以通过使用初始化列表来实现性能的提升。我们先来看一段程序:
         class Person
    {
    private:
         C c_1;
         C c_2;
    public:
         Person(const C& c1, const C& c2 ): c_1(c1), c_2(c2) {}
    };
    当然构造函数我们也可以这样写:
    Person::Person(const C& c1, const C& c2)
    {
        c_1 = c1;
        c_2 = c2;
    }
    那么究竟二者会带来什么样的性能差异呢,要想搞清楚这个问题,我们首先要搞清楚二者是如何执行的,先来看初始化列表:数据成员的声明操作都是在构造函数执行之前就完成了,在构造函数中往往完成的只是赋值操作,然而初始化列表直接是在数据成员声明的时候就进行了初始化,因此它只执行了一次copy constructor。再来看在构造函数中赋值的情况:首先,在构造函数执行前会通过default constructor创建数据成员,然后在构造函数中通过operator =进行赋值。因此它就比初始化列表多进行了一次函数调用。性能差异就出来了。但是请注意,如果你的数据成员都是基本类型的话,那么为了程序的可读性就不要使用初始化列表了,因为编译器对两者产生的汇编代码是相同的。
    (4) postfix VS prefix 运算符
    prefix运算符++和—比它的postfix版本效率更高,因为当postfix运算符被使用的时候,会需要一个临时对象来保存改变以前的值。对于基本类型,编译器会消除这一份额外的拷贝,但是对于用户定义类型,这似乎是不可能的。因此请你尽可能使用prefix运算符。

    三. 内联函数
    内联函数既能够去除函数调用所带来的效率负担又能够保留一般函数的优点。然而,内联函数并不是万能药,在一些情况下,它甚至能够降低程序的性能。因此在使用的时候应该慎重。
        1.我们先来看看内联函数给我们带来的好处:从一个用户的角度来看,内联函数看起来和普通函数一样,它可以有参数和返回值,也可以有自己的作用域,然而它却不会引入一般函数调用所带来的负担。另外,它可以比宏更安全更容易调试。
     当然有一点应该意识到,inline specifier仅仅是对编译器的建议,编译器有权利忽略这个建议。那么编译器是如何决定函数内联与否呢?一般情况下关键性因素包括函数体的大小,是否有局部对象被声明,函数的复杂性等等。
        2.那么如果一个函数被声明为inline但是却没有被内联将会发生什么呢?理论上,当编译器拒绝内联一个函数的时候,那个函数会像普通函数一样被对待,但是还会出现一些其他的问题。例如下面这段代码:
        // filename Time.h
    #include<ctime>
    #include<iostream>
    using namespace std;
    class Time
    {
    public:
        inline void Show() { for (int i = 0; i<10; i++) cout<<time(0)<<endl;}
    };
        因为成员函数Time::Show()包括一个局部变量和一个for循环,所以编译器一般拒绝inline,并且把它当作一个普通的成员函数。但是这个包含类声明的头文件会被单独的#include进各个独立的编译单元中:
        // filename f1.cpp
    #include "Time.hj"
    void f1()
    {
        Time t1;
        t1.Show();
    }

    // filename f2.cpp
    #include "Time.h"
    void f2()
    {
        Time t2;
        t2.Show();
    }
    结果编译器为这个程序生成了两个相同成员函数的拷贝:
    void f1();
    void f2();
    int main()
    {
        f1();
        f2();
      return 0;
    }
    当程序被链接的时候,linker将会面对两个相同的Time::Show()拷贝,于是函数重定义的连接错误发生。但是老一些的C++实现对付这种情况的办法是通过把一个un-inlined函数当作static来处理。因此每一份函数拷贝仅仅在自己的编译单元中可见,这样链接错误就解决了,但是在程序中却会留下多份函数拷贝。在这种情况下,程序的性能不但没有提升,反而增加了编译和链接时间以及最终可执行体的大小。
    但是幸运的是,新的C++标准中关于un-inlined函数的说法已经改变。一个符合标准C++实现应该只生成一份函数拷贝。然而,要想所有的编译器都支持这一点可能还需要很长时间。
    另外关于内联函数还有两个更令人头疼的问题。第一个问题是该如何进行维护。一个函数开始的时候可能以内联的形式出现,但是随着系统的扩展,函数体可能要求添加额外的功能,结果内联函数就变得不太可能,因此需要把inline specifier去除以及把函数体放到一个单独的源文件中。另一个问题是当内联函数被应用在代码库的时候产生。当内联函数改变的时候,用户必须重新编译他们的代码以反映这种改变。然而对于一个非内联函数,用户仅仅需要重新链接就可以了。
    这里想要说的是,内联函数并不是一个增强性能的灵丹妙药。只有当函数非常短小的时候它才能得到我们想要的效果,但是如果函数并不是很短而且在很多地方都被调用的话,那么将会使得可执行体的体积增大。最令人烦恼的还是当编译器拒绝内联的时候。在老的实现中,结果很不尽人意,虽然在新的实现中有很大的改善,但是仍然还是不那么完善的。一些编译器能够足够的聪明来指出哪些函数可以内联哪些不能,但是,大多数编译器就不那么聪明了,因此这就需要我们的经验来判断。如果内联函数不能增强行能,就避免使用它!
    四. 优化你的内存使用
    通常优化都有几个方面:更快的运行速度,有效的系统资源使用,更小的内存使用。一般情况下,代码优化都是试图在以上各个方面进行改善。重新放置声明技术被证明是消除多余对象的建立和销毁,这样既减小了程序的大小又加快了运行速度。然而其他的优化技术都是基于一个方面------更快的速度或者是更小的内存使用。有时,这些目标是互斥的,压缩了内存的使用往往却减慢了代码速度,快速的代码却又需要更多的内存支持。下面总结两种在内存使用上的优化方法:
    1. Bit Fields
    在C/C++中都可以存取和访问数据的最小组成单元:bit。因为bit并不是C/C++基本的存取单元,所以这里是通过牺牲运行速度来减少内存和辅助存储器的空间的使用。注意:一些硬件结构可能提供了特殊的处理器指令来存取bit,因此bit fields是否影响程序的速度取决于具体平台。
    在我们的现实生活中,一个数据的许多位都被浪费了,因为某些应用根本就不会有那么大的数据范围。也许你会说,bit是如此之小,通过它就能减小存储空间的使用吗?的确,在数据量很小的情况下不会看出什么效果,但是在数据量惊人的情况下,它所节省的空间还是能够让我们的眼睛为之一亮的。也许你又会说,现在内存和硬盘越来越便宜,何苦要费半天劲,这省不了几个钱。但是还有另外一个原因一定会使你信服,那就是数字信息传输。一个分布式数据库都会在不同的地点有多份拷贝。那么数百万的纪录传输就会显得十分昂贵。Ok,现在我们就来看看该如何做吧,首先看下面这段代码:
    struct BillingRec
    {
         long cust_id;
         long timestamp;
         enum CallType
          {
            toll_free,
             local,
              regional,
              long_distance,
           international,
              cellular
      } type;
            enum CallTariff
           {
            off_peak,
              medium_rate,
              peak_time
        } tariff;
    };
    上面这个结构体在32位的机器上将会占用16字节,你会发现其中有许多位都被浪费了,尤其是那两个enum型,浪费更是严重,所以请看下面做出的改进:
    struct BillingRec
    {
      int cust_id: 24; // 23 bits + 1 sign bit
      int  timestamp: 24;
         enum CallType
          {//...
          };
          enum CallTariff
             {//...
          };
          unsigned call: 3;
            unsigned tariff: 2;
    };
    现在一个数据从16字节缩减到了8字节,减少了一半,怎么样,效果还是显著的吧:)
    2. Unions
    Unions通过把两个或更多的数据成员放置在相同地址的内存中来减少内存浪费,这就要求在任何时间只能有一个数据成员有效。Union 可以有成员函数,包括构造函数和析构函数,但是它不能有虚函数。C++支持anonymous unions。anonymous union是一个未命名类型的未命名对象。例如:
    union { long n; void * p};  // anonymous
    n = 1000L;  // members are directly accessed
    p = 0; // n is now also 0
    不像命名的union,它不能有成员函数以及非public的数据成员。
    那么unions什么时候是有用的呢?下面这个类从数据库中获取一个人的信息。关键字既可以是一个特有的ID或者人名,但是二者却不能同时有效:
    class PersonalDetails
    {
    private:
      char * name;
      long ID;
         //...
    public:
         PersonalDetails(const char *nm);  //key is of type char * used
         PersonalDetails(long id) : ID(id) {} //numeric key used 
    };
    上面这段代码中就会造成内存的浪费,因为在一个时间只能有一个关键字有效。anonymous union可以在这里使用来减少内存的使用,例如:
    class PersonalDetails
    {
    private:
         union  //anonymous
      {
           char * name;
            long ID;
      };
    public:
      PersonalDetails(const char *nm);
           PersonalDetails(long id) : ID(id) {/**/}  // direct access to a member
         //...
    };
    通过使用union,PersonalDetails类的大小被减半。但是这里要说明的是,节省4 个字节内存并不值得引入union所带来的麻烦,除非这个类作为数百万数据库记录的类型或者纪录在一条很慢的通信线路传输。值得注意的是unions并不引入任何运行期负担,所以这里不会有什么速度上的损失。anonymous union的优点就是它的成员可以被直接访问。

    五. 速度优化
    在一些对速度要求非常苛刻的应用系统中,每一个CPU周期都是要争取的。这个部分展现了一些简单方法来进行速度优化。
    1. 使用类来包裹长的参数列表
    一个函数调用的负担将会随着参数列表的增长而增加。运行时系统不得不建立堆栈来存储参数值;通常,当参数很多的时候,这样一个操作就会花费很长的时间。
    把参数列表包裹进一个单独的类中并且通过引用进行传递,这样将会节省很多的时间。当然,如果函数本身就很长,那么建立堆栈的时间就可以忽略了,因此也就没有必要这样做。然而,对于那些执行时间很短而且经常被调用的函数来说,包裹一个长的参数列表在对象中并且通过引用传递将会提高性能。
    2. 寄存器变量
    register specifier被用来告诉编译器一个对象将被会非常多的使用,可以把它放入寄存器中。例如:
    void f()
    {
      int *p = new int[3000000];
         register int *p2 = p; //store the address in a register
         for (register int j = 0; j<3000000; j++)
           {
            *p2++ = 0;
         }
         //...use  p 
         delete [] p;
    }
    循环计数是应用寄存器变量的最好的候选者。当它们没有被存入一个寄存器中,大部分的循环时间都被用在了从内存中取出变量和给变量赋新值上。如果把它存入一个寄存器中的话,将会大大减少这种负担。需要注意的是,register specifier仅仅是对编译器的一个建议。就好比内联函数一样,编译器可以拒绝把一个对象存储到寄存器中。另外,现代的编译器都会通过把变量放入寄存器中来优化循环计数。Register storage specifier并不仅仅局限在基本类型上,它能够被应用于任何类型的对象。如果对象太大而不能装进寄存器的话,编译器仍然能够把它放入一个高速存储器中,例如cache。
    用register storage specifier声明函数型参将会是建议编译器把实参存入寄存器中而不是堆栈中。例如:

    void f(register int j, register Date d);

    3. 把那些保持不变的对象声明为const
    通过把对象声明为const,编译器就可以利用这个声明把这样一个对象放入寄存器中。
    4. Virtual function的运行期负担
    当调用一个virtual function,如果编译器能够解决调用的静态化,将不会引入额外的负担。另外,一个非常短的虚函数可以被内联处理。在下面这个例子中,一个聪明的编译器能够做到静态调用虚函数:
    #include <iostream>
    using namespace std;
    class V
    {
    public: 
      virtual void show() const { cout<<"I'm V"<<endl; }
    };
    class W : public V
    {
    public:
      void show() const { cout<<"I'm W"<<endl; }
    };
    void f(V & v, V *pV)
    {
      v.show();  
      pV->show(); 
    }
    void g()
    {
         V v;
         f(v, &v);
    }
    int main()
    {
      g();
      return 0;
    }
    如果整个程序出现在一个单独的编译单元中,编译器能够对main()中的g()进行内联替换。并且在g()中f()的调用也能够被内联处理。因为传给f()的参数的动态类型能够在编译期被知晓,因此编译器能够把对虚函数的调用静态化。但是不能保证每个编译器都这样做。然而,一些编译器确实能够利用在编译期获得参数的动态类型从而使得函数的调用在编译期间就确定了下来,避免了动态绑定的负担。
    5. Function objects VS function pointers
    用function objects取代function pointers的好处不仅仅局限在能够泛化和简单的维护性上。而且编译器能够对function object的函数调用进行内联处理,从而进一步的增强了性能
    六. 最后的求助
    迄今为止为大家展示的优化技术并没有在设计以及代码的可读性上做出妥协。事实上,它们中的一些还提高了软件的稳固性和可维护性。但是在一些对时间和内存有严格限制的软件开发中,上面的技术可能还不够;有可能还需要一些会影响软件的可移植性和扩展性的技术。但是这些技术只能在所有其他的优化技术都被应用但是还不符合要求的情况下使用。
    1. 关闭RTTI和异常处理支持
    当你导入纯C代码给C++编译器的时候,你可能会发现有一些性能上的损失。这并不是语言或者编译器的错误,而是编译器作出的一些调整。如果你想获得和C编译器同样的性能,那么请关闭编译器对RTTI以及异常处理的支持。为什么会这样呢?因为为了支持RTTI和异常处理,C++编译器会插入额外的代码。这样就增加了可执行体的大小,从而使得效率有所下降。当应用纯C代码的时候,那些额外的代码是不需要的,所以你可以通过关闭来避免它。
    2. 内联汇编
    对时间要求苛刻的部分可以用本地汇编来重写。结果可能是速度上的显著提高。然而,这个方法不能想当然的就去实施,因为它将使得将来的修改非常的困难。维护代码的程序员可能对汇编并不了解。如果想要把软件运行于其他平台也需要重写汇编代码部分。另外,开发和测试汇编代码是一件辛苦的工作,它将花费更长的时间。
    3. 直接和操作系统进行交互
    API函数可以使你直接与操作系统进行交互。有时,直接执行一个系统命令可能会快许多。出于这个目的,你可以使用标准函数system()。例如,在一个dos/windows系统下,你可以这样显示当前目录下的文件:
    #include <cstdlib>
    using namespace std;
    int main()
    {
      system("dir");  //execute the "dir" command
    }
    注意:这里是在速度和可移植性以及可扩展性之间做出的折衷。

    展开全文
  • 常用代码优化方法

    千次阅读 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); 推荐

    展开全文
  • 以基本块为单位,进行运算上的推导优化。堪称妙!原来编译器这么强大!

    本次笔记内容:
    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 “逆流而上”,发现 4i4*i 没有被修改过,则其是一个公共子表达式。

    进行了再次的画家如上。

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




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

    a[t1]a[t_1]作为公共子表达式是不稳妥的,因为控制离开B1B_1进入B6B_6之前可能进入B5B_5,而B5B_5有对aa的赋值。



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

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

    常用的代码优化方法(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)。


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

    ⑥删除归纳变量

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


    如上,iijj 都无用了。

    基本块的优化

    很多重要的局部优化技术首先把一个基本块转换成为一个无环有向图(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=ji=j ,因此不能轻易把 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
    
    展开全文
  • DSP代码优化方法(2)

    千次阅读 2011-09-15 12:09:22
    在实际的DSP应用中,许多算法都是非常复杂,直接用汇编代码编写,虽然优化效率很高,可是实现的难度却很大,所以一般都采用先用C语言来实现,然后编译运行,利用C64X开发环境的profile?clock工具测试程序
  • 3. 不要把方法细分得过多,仔细想想你真正打算重用的是哪些代码? 4. 在执行for循环之前确定最大循环数,不要每循环一次都计算最大值。 5. 注销那些不用的变量尤其是大数组,以便释放内存。 6. 并非要用类实现...
  • 前端代码优化基本方法

    千次阅读 2017-10-12 13:51:20
    前端性能优化
  • 一些代码优化方法

    千次阅读 2016-01-13 14:30:59
    当然不是,C++层次一样可以作代码优化,其中有些常常是意想不到的。在C++层次进行优化,比在汇编层次优化具有更好的移植性,应该是优化中的首选做法。 1 确定浮点型变量和表达式是 float 型 为了让编译器产生更好...
  • linux下GCC编译代码优化方法总结

    千次阅读 2013-08-16 21:12:26
    代码优化指的是编译器通过分析源代码,找出其中尚未达到最优的部分,然后对其重新进行组合,目的是改善程序的执行性能。  GCC提供的代码优化功能非常强大,它通过编译选项-On来控制优化代码的生成,其中n是一个代表优化...
  • 代码优化---取余(%)优化

    千次阅读 热门讨论 2016-02-26 11:24:17
    代码优化---取模运算优化方法
  • 优化Python代码的4种方法

    千次阅读 2019-10-05 15:09:50
    因此,在本文中,我将借鉴我多年的编程经验来列出并展示四种可用于优化数据科学项目中Python代码方法优化是什么? 首先定义什么是优化。我们将使用一个直观的示例进行此操作。 这是我们的问题: 假...
  • Keil优化代码大小方法

    万次阅读 2015-07-17 10:27:36
    如图 1,未进行任何优化时,keil编译生成的文件大小为:9668字节。 第一步:project >> Option for Target “**” 打开如图 2界面。选择“target”,勾选上“Use MicroLIB”再编译。分析:microLIB是缺省的C库,...
  • ccs代码优化

    千次阅读 2012-08-25 16:08:39
    进行代码优化,先要找出程序的瓶颈,即占用CPU时间较多的代码,然后对其进行有针对性的优化。使用CCS提供的代码剖析工具Profile可以统计显示出程序中各个重要段和函数的运行时间,找出运算量较大的程序段,优化这些...
  • Unity性能优化(一)-脚本代码优化

    千次阅读 2018-05-14 18:27:41
    Unity性能优化-脚本代码优化 参考文献:https://unity3d.com/cn/learn/tutorials/topics/performance-optimization 脚本代码优化 托管代码和托管运行时简介 在Unity工程的 构建(Build) 过程中,C# ...
  • 前端代码优化

    千次阅读 2019-04-14 16:48:12
    上次写了浏览器渲染出网页的过程,想到了一些代码优化的小知识,当然了,我学识太浅,想到了还是太少了,所以这篇文章就长期更新啦。以后可能还有其他的框架如React等单独的代码优化的知识,毕竟这样的知识实在是太...
  • 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; ...
  • 来自:www.cnblogs.com/xrq730/代码优化的最重要的作用应该是:避免未知的错误在代码上线运行的过程中,往往会出现很多我们意想不到的错误,因为线上环境和开...
  • 我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。 但是这个共有方法不应该让实现类使用,应该是私有化的。 二.解决方案: 从Java 9以上的版本,接口当中允许定义私有方法。 普通私有...
  • 代码优化:重复代码封装成函数

    千次阅读 2016-04-07 21:48:20
    代码优化:重复代码封装成函数
  • 编译原理之代码优化

    万次阅读 多人点赞 2017-12-18 10:43:49
    编译原理出于代码编译的模块化组装考虑,一般会在语义分析的阶段生成平台无关的中间代码,经过中间代码级的代码优化,而后作为输入进入代码生成阶段,产生最终运行机器平台上的目标代码,再经过一次目标代码级别的...
  • 最近写了一个模式识别的程序,可是发现时间复杂度有点...现在比较通用的两种代码优化方法是:(1)向量化循环;(2)预分配数组 (1)向量化循环: 向量化即是将for循环和while循环转换为等价的向量或矩阵运算,它可以
  • Python 代码性能优化技巧

    千次阅读 2012-10-10 16:37:31
    选择了脚本语言就要忍受其速度,这句话在某种程度上说明了 python 作为脚本的一个不足之处,...本文会涉及常见的代码优化方法,性能优化工具的使用以及如何诊断代码的性能瓶颈等内容,希望可以给 Python 开发人员一定的
  • Simulink自动生成代码的常见优化方法

    万次阅读 多人点赞 2018-08-24 20:19:11
     上一篇文章详细学习了如何通过Simulink建立系统模型,进而生成嵌入式代码。本文通过实例进一步加深对代码自动生成的理解和应用。 一、建立系统框图  为了方便起见,在这里我们实现一个的简单算法,我们在simu.....
  • 优化方法及其Matlab程序设计程序代码课件 《最优化方法及其Matlab程序设计》较系统地介绍了非线性最优化问题的基本理论和算法,以及主要算法的Matlab程序设计,主要内容包括(精确或非精确)线搜索技术、最速下降...
  • JAVA代码优化性能的方法

    千次阅读 2018-12-07 23:55:11
    3 >finally块中避免再次抛出异常,否则整个包含try语句块的方法回抛出异常,并且会消化掉try、catch块中的异常 6.奇数判断  使用 (num&1)!=0 因为如果一个数是偶数的话那么它二进制的最后一位就是0,这样判断...
  • Web前端开发学习3:SEO代码优化

    千次阅读 2015-10-31 18:28:17
    代码优化概述  关于代码优化的知识是纯理论的知识,学习的很枯燥。在学到CSS时,不免遇到CSS+div进行代码优化的知 识,因此在网上看了一些关于这方面的知识,简单的整合一下,梳理自己所了解的代码优化问题。  ...
  • Java代码优化提点

    万次阅读 2013-10-30 11:04:16
     代码优化是指对程序代码进行等价(指不改变程序的运行结果)变换。程序代码可以是中间代码,也可以是目标代码。等价的含义是使得变换后的代码运行结果与变换前代码运行结果相同。优化的含义是最终生成的目标代码...
  • 编译原理 代码优化

    千次阅读 2018-10-22 01:34:29
    代码优化: 因为抽象语法树中可能包括错误,因此不能在抽象语法树阶段进行优化。 函数式的优化:输入一个抽象语法树,输出一个抽象语法树: 在循环中,如果E仍在缩小,就持续常量折叠。 本来...
  • 通过java代码规范来优化程序,优化内存使用情况,防止内存泄露 可供程序利用的资源(内存、CPU时间、网络带宽等)是有限的,优化的目的就是让程序用尽可能少的资源完成预定的任务。优化通常包含两方面的内容:减小...
  • HIT软件构造 代码优化

    千次阅读 2019-05-30 03:29:05
    它其实式通过全局变量进行的优化。 节省创建新的对象的时间 系统内存的使用也会减少 实现方法: 设置静态和 final变量来存储单一实例对象 将构造器设置为private,提供静态方法来获取单一实例对象 Flyweight ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,066,027
精华内容 426,410
关键字:

代码优化的方法