• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            More Effective C++ (1)

            Posted on 2010-01-18 14:16 rikisand 閱讀(336) 評(píng)論(0)  編輯 收藏 引用 所屬分類: 工作記錄~~everydayC/C++

            第四章  效率

            ······條款16 記住80-20準(zhǔn)則

            大約20%的代碼使用了80%的資源,程序的整體性能是由該程序的一小部分代碼所決定的~

            可行的辦法是使用程序分析器(profiler)來(lái)找到導(dǎo)致性能瓶頸的拿20%的程序~

            而且要針對(duì)造成瓶頸的資源來(lái)使用相應(yīng)的分析器~

             

            ······條款17  考慮使用延遲計(jì)算

            延遲計(jì)算: 也就是說(shuō)知道程序要求給出結(jié)果的時(shí)候才進(jìn)行運(yùn)算~ 很好理解,和操作系統(tǒng)中的cow copy on write 一個(gè)原理~

            四個(gè)使用場(chǎng)景:

            ~1~ 引用計(jì)數(shù) :

              class String{…};

            String s1 = “hello”;

            String s2 = s1 ;      //call string Copy ctor

            通常情況下,s2賦值后會(huì)有一個(gè)hello的拷貝,者通常需要使用new操作符分配內(nèi)存,之后strcpys1

            的數(shù)據(jù)給他,但如果下面的操作如下的話:

            cout << s1 ;

            cout << s1 + s2;

            這種情況下如果只增加s1的引用計(jì)數(shù),而s2只是共享s1的值就可以了。只有在需要對(duì)s2進(jìn)行修改或者s1進(jìn)行修改時(shí),才需要真正拷貝給s2一個(gè)副本,引用計(jì)數(shù)的實(shí)現(xiàn)在29條款

            ~2~區(qū)分讀寫操作

            如: String s = “homer’s all”;

            cout<< s[3];

            s[3] = ‘x’;

            在進(jìn)行讀操作時(shí),使用引用計(jì)數(shù)是開(kāi)銷很小的,然而寫操作必須生成新的拷貝。通過(guò)條款30的代理類我們可以把判斷讀寫操作推遲到我們能夠決定哪個(gè)是正確操作的時(shí)候

            ~3~延遲讀取

            假設(shè)程序使用了包含許多數(shù)據(jù)成員的大對(duì)象,這些對(duì)象必須在每次程序運(yùn)行的時(shí)候保留下來(lái),因此存進(jìn)了數(shù)據(jù)庫(kù)。某些時(shí)候從database中取出所有數(shù)據(jù)是沒(méi)有必要的,比如他只訪問(wèn)該對(duì)象中的一個(gè)數(shù)據(jù)成員。此時(shí),應(yīng)該對(duì)對(duì)象進(jìn)行處理,只有對(duì)象內(nèi)部某一個(gè)特定的數(shù)據(jù)成員被訪問(wèn)的時(shí)候才把他取出來(lái)。類似于os中的按需換頁(yè)~

            class LargeObject{

                LargeObject(ObjectID id);

            const string& field1() const;

            int field2() const;

            double field3() const;

            const string& field4() const;

            private:

            ObjectID id;

            mutable string* field1value;

            mutable int   * fieldValue;

            };

            LargeObject::LargeObject(ObjectID id):oid(id),fieldValue(0),…{}

            const string& LargeObject::field1()const{

               if(fieldValue == 0){

                   //read the data for field 1 from database and make field1 point to it

               }

               return  *field1Value;

            }

            實(shí)施lazy fetching 任何成員函數(shù)都需要初始化空指針以指向有效數(shù)據(jù)。但是const成員函數(shù)中,試圖修改數(shù)據(jù)編譯器會(huì)報(bào)錯(cuò)。所以聲明字段指針為 mutable ,表示任何函數(shù)都可以修改,即便在const成員函數(shù)中也可以~ 條款28中的智能指針可以讓這一方法更靈活

            ~3~延遲表達(dá)式求值

            數(shù)值計(jì)算領(lǐng)域,也在使用延遲計(jì)算。例如

            matrix<int> m1(1000,1000);

            matrix<int> m2(1000,1000);

            matrix<int> m3 = m1 + m2;

            如果此時(shí)計(jì)算出m3的話運(yùn)算量非常之大~

            但是如果此時(shí)程序?yàn)椋?/font>

            m3 = m4*m1;

            那么剛才的計(jì)算就沒(méi)必要了

            如果cout<< m3[4];

            我們只需要計(jì)算m3[4]就可以了,其他的值等到確實(shí)需要他們的時(shí)候才予以計(jì)算~如果運(yùn)氣夠好的話永遠(yuǎn)不需要計(jì)算~

            總結(jié),延遲計(jì)算只有當(dāng)軟件在某種程度下可以被避免時(shí)候才有意義~只有延遲計(jì)算得到的好處大于設(shè)計(jì)它與實(shí)現(xiàn)它花費(fèi)的精力時(shí)才有意義~

            ·······條款18: 分期攤還預(yù)期的計(jì)算開(kāi)銷

            提前計(jì)算~ over-eager evaluation 在系統(tǒng)要求你做某些事情之前就做了他~

            例如:大量數(shù)據(jù)的集合

            template<class NumericalType>

            class DataCollection}{

            public:

              NumericalType min() const;

              NumericalType max() const;

              NumericalType avg() const;

            };

            使用提前計(jì)算,我們隨時(shí)跟蹤目前集合的最大最小平均值,這樣 min max avg被調(diào)用時(shí)候,我們可以不用計(jì)算立刻返回正確的數(shù)值~~

            提前計(jì)算的思想便是:如果預(yù)計(jì)某個(gè)計(jì)算會(huì)被頻繁調(diào)用,你可以通過(guò)設(shè)計(jì)你的數(shù)據(jù)結(jié)構(gòu)以更高效的辦法處理請(qǐng)求,這樣可以降低每次請(qǐng)求的平均開(kāi)銷~

            最簡(jiǎn)單的做法為 緩存已經(jīng)計(jì)算過(guò)并且很可能不需要重新計(jì)算的那些值~

            例如在數(shù)據(jù)庫(kù)中存有很多辦公室的電話號(hào)碼,程序在每次查詢電話時(shí)先查詢本地的緩存如果沒(méi)找到再去訪問(wèn)數(shù)據(jù)庫(kù),并且更新緩存,這樣使用緩存平均訪問(wèn)時(shí)間要大大減小。

            預(yù)處理也是一種策略。

            例如設(shè)計(jì)動(dòng)態(tài)數(shù)組的時(shí)候,當(dāng)索引下標(biāo)大于已有最大范圍時(shí)候,需要new出新的空間,如果申請(qǐng)兩倍于索引的大小的話就可以避免頻繁的申請(qǐng)操作~~~

             

            ········條款 19 : 了解臨時(shí)對(duì)象的來(lái)源

            如果一個(gè)對(duì)象被創(chuàng)建,不是在堆上,沒(méi)有名字,那么這個(gè)對(duì)象就是臨時(shí)對(duì)象。

            通常產(chǎn)生于: 為了使函數(shù)調(diào)用能夠成功而進(jìn)行的隱式轉(zhuǎn)換,或者函數(shù)返回對(duì)象是進(jìn)行的隱式轉(zhuǎn)換。由于構(gòu)造和析構(gòu)他們帶來(lái)的開(kāi)銷可以給你的程序帶來(lái)顯著的影響,因此有必要了解他們~

            ~1首先考慮為了函數(shù)調(diào)用能通過(guò)產(chǎn)生的臨時(shí)對(duì)象的情況

            傳給某個(gè)函數(shù)的對(duì)象的類型和這個(gè)函數(shù)所綁定的參數(shù)類型不一致的情況下會(huì)出現(xiàn)這種情況。

            例如:

            size_t count(const string& str,char ch);

            函數(shù)定義為計(jì)算str中ch的數(shù)量

            char buffer[100];

            cout<<count(buffer,‘c’);

            傳入的是一個(gè)char數(shù)組,此時(shí)編譯器會(huì)調(diào)用str的構(gòu)造函數(shù),利用buffer來(lái)創(chuàng)建一個(gè)臨時(shí)對(duì)象。

            在調(diào)用完countChar語(yǔ)句后這個(gè)臨時(shí)對(duì)象就被自動(dòng)銷毀了~

            僅當(dāng)傳值或者const引用的時(shí)候才會(huì)發(fā)生這樣的類型轉(zhuǎn)換~當(dāng)傳遞一個(gè)非常量引用的時(shí)候,不會(huì)發(fā)生。

            void uppercasify(string& str); //change all chars in str to upper case;

            在這個(gè)例子中使用char數(shù)組就不會(huì)成功~

            因?yàn)槌绦蜃髡呗暶鞣浅A恳靡簿褪窍胱寣?duì)引用的修改反映在他引用的對(duì)象身上,但是如果此時(shí)生成了臨時(shí)對(duì)象,那么這些修改只是作用在臨時(shí)對(duì)象身上,也就不是作者的本意了。所以c++禁止非常量引用產(chǎn)生臨時(shí)對(duì)象。

            ~2 函數(shù)返回對(duì)象時(shí)候會(huì)產(chǎn)生臨時(shí)對(duì)象

            例如: const Number operator + ( const Number& lhs,const Number& rhs);

            這個(gè)函數(shù)返回一個(gè)臨時(shí)對(duì)象,因?yàn)樗麤](méi)有名字,只是函數(shù)的返回值。

            條款20中 ,會(huì)介紹讓編譯器對(duì)已經(jīng)超出生存周期的臨時(shí)對(duì)象進(jìn)行優(yōu)化

             

            ········條款20: 協(xié)助編譯器實(shí)現(xiàn)返回值優(yōu)化

            返回值優(yōu)化:返回帶有參數(shù)的構(gòu)造函數(shù)。

            cosnt Rational operator * (cosnt Rational& lhs,const Rational& rhs){

                return Rational(lhs.numerator()*rhs.numerator(),lhs.denomiator()*rhs.denominator()};

            c++允許編譯器針對(duì)超出生命周期的臨時(shí)對(duì)象進(jìn)行優(yōu)化。因此如果調(diào)用Rational c=a*b;

            c++允許編譯器消除operator*內(nèi)部的臨時(shí)變量以及operator*返回的臨時(shí)變量,編譯器可以把return表達(dá)式所定義的返回對(duì)象構(gòu)造在分配給c的內(nèi)存上。如果這樣做的話那么調(diào)用operator*所產(chǎn)生的臨時(shí)對(duì)象所帶來(lái)的開(kāi)銷就是0~ 我們可以把operator 聲明為內(nèi)聯(lián)函數(shù)而去除調(diào)用構(gòu)造函數(shù)帶來(lái)的開(kāi)銷~

            #include <iostream>
            #include <string>
            #include "time.h"
            using namespace std;
            char buffer[100];
            class number{
            public:
                const friend  number operator * (const number& rhs,const number lhs);
                number(){}
                number(int b):a(b){}
                number(const number& rhs){
                    a = rhs.a;
                }
                  int a;
            };
            const number operator*(const number& rhs,const number lhs){
                number res;
                res.a = rhs.a * lhs.a;
                    return res;
                /*return number(rhs.a*lhs.a);*/
            }
            //CLOCKS_PER_SEC
            int main()
            {
                clock_t start = clock();
                number A(5);number B(6);
                for(int i=0;i<100000000;i++)
                    number C = A*B;

                clock_t end = clock();
                cout<<double(end-start)/CLOCKS_PER_SEC<<endl;
            }

            通過(guò)上面的程序運(yùn)行 如果沒(méi)有返回值優(yōu)化 運(yùn)行時(shí)間 15.9s 優(yōu)化后是 10.1s

            還是很顯著的么 快了33% ,如果這種情況出現(xiàn)在程序的熱點(diǎn)處~效果就很好了

             

            ·········條款21 : 通過(guò)函數(shù)重載避免隱式類型轉(zhuǎn)換

            例子:

            class upint{

            public:

            upint();

            upint(int value);

            };

            cosnt upint operator+(const upint&lhs,const upint&rhs);

            upint up1,up2;

            upint up3 = up1+up2;

            upi3 = up1 +10;

            upi4 = 10+ upi2;

            這些語(yǔ)句也可以通過(guò),因?yàn)閯?chuàng)建了臨時(shí)對(duì)象,通過(guò)帶有int的構(gòu)造函數(shù)產(chǎn)生了臨時(shí)的upint對(duì)象,如果我們不愿意為這些臨時(shí)對(duì)象的產(chǎn)生與析構(gòu)付出代價(jià),我們需要做什么:

            我們聲明 cosnt upint operator+(cosnt upint&lhs,int rhs);

            cosnt upint operator+(int lhs,const upint& rhs);

            就可以去除臨時(shí)對(duì)象產(chǎn)生了~

            但是如果我們寫了 const upint operator+(int lhs,int rhs); // 錯(cuò)了~

            c++規(guī)定,每一個(gè)被重載的運(yùn)算符必須至少有一個(gè)參數(shù)屬于用戶自定義類型,int并不是自定義類型所以上面的不對(duì)的

            同樣的如果希望string char* 作為參數(shù)的函數(shù),都有理由進(jìn)行重載而避免隱形類型轉(zhuǎn)換(僅僅在有必要的時(shí)候,也就是說(shuō)他們可以對(duì)程序效率起到很大幫助的時(shí)候~)

            ··········條款: 考慮使用 op = 來(lái)取代 單獨(dú)的 op運(yùn)算符

            class Rational{

            public:

               Rational& operator+=(const Rational& rhs);

               Rational& operator-=(const Rational& rhs);

            }

            const Rational operator+(cosnt Rational& lhs,const Rational & rhs){

                return Rational(lhs)+=rhs;

            }

            利用+= -=來(lái)實(shí)現(xiàn)+ -可以保證運(yùn)算符的賦值形式與單獨(dú)使用運(yùn)算符之間存在正常的關(guān)系。

            Rational a,b,c,d,result;

            result = a+ b+c+d; // 可能要用到3個(gè)臨時(shí)對(duì)象

            result +=a;result+=b;result+=c; //沒(méi)有臨時(shí)對(duì)象

            前者書寫維護(hù)都更容易,而且一般來(lái)說(shuō)效率不存在問(wèn)題,但是特殊情況下后者效率更高更可取

            注意:

            如果+的實(shí)現(xiàn)是這樣的:

            const T operator+ (constT& lhs,const T&rhs){

                 T result(lhs);

                 return result += rhs;

            }

            這個(gè)模版中包含有名字對(duì)象result,這個(gè)對(duì)象有名字意味著返回值優(yōu)化不可用~~~~~~~~·

             

            ·······條款23 : 考慮使用其他等價(jià)的程序庫(kù)

            主旨:

            提供類似功能的程序庫(kù)通常在性能問(wèn)題上采取不同的權(quán)衡措施,比如iostream和stdio,所以通過(guò)分析程序找到軟件瓶頸之后,可以考慮是否通過(guò)替換程序庫(kù)來(lái)消除瓶頸~~~~

             

             

            ······條款24 : 理解虛函數(shù),多重繼承,虛基類以及 RTTI 帶來(lái)的開(kāi)銷

            虛函數(shù)表:vtabs 指向虛函數(shù)表的指針 vptrs

            程序中每個(gè)聲明了或者繼承了的虛函數(shù)的類都具有自己的虛函數(shù)表。表中的各個(gè)項(xiàng)就是指向虛函數(shù)具體實(shí)現(xiàn)的指針。

            class c1{

               c1();

               virtual ~c1();

               virtual void f1();

               virtual int f2(char c)const;

               virtual void f3(const string& s);

            };

            c1 的虛函數(shù)表包括: c1::~c1 c1::f1 c1::f2 c1::f3

            class c2:public c1{

               c2();

               virtual ~c2();

               virtual void f1();

               virtual void f5(char *str);

            };

            它的虛函數(shù)表入口指向的是那些由c1聲明但是c2沒(méi)有重定義的虛函數(shù)指針:

            c2::~c2  c2::f1 c1::f2 c1::f3 c2::f5

            所以開(kāi)銷上: 必須為包含虛函數(shù)的類騰出額外的空間來(lái)存放虛函數(shù)表。一個(gè)類的虛函數(shù)表的大小取決于它的虛函數(shù)的個(gè)數(shù),雖然每一個(gè)類只要有一個(gè)虛函數(shù)表,但是如果有很多類或者每個(gè)類具有很多個(gè)虛函數(shù),虛函數(shù)表也會(huì)占據(jù)很大的空間,這也是mfc沒(méi)有采用虛函數(shù)實(shí)現(xiàn)消息機(jī)制的一個(gè)原因。

            由于每一個(gè)類只需要一個(gè)vtbl的拷貝,把它放在哪里是一個(gè)問(wèn)題:

            一種:為每一個(gè)需要vtbl的目標(biāo)文件生成拷貝,然后連接時(shí)取出重復(fù)拷貝

            或者:更常見(jiàn)的是采用試探性算法決定哪一個(gè)目標(biāo)文件應(yīng)該包含類的vtbl。試探:一個(gè)類的vtbl通常產(chǎn)生在包含該類第一個(gè)非內(nèi)聯(lián),非純虛函數(shù)定義的目標(biāo)文件里。所以上面c1類的vtbl將放在c1::~c1 定義的目標(biāo)文件里。如果所有虛函數(shù)都聲明為內(nèi)聯(lián),試探性算法就會(huì)失敗,在每一個(gè)目標(biāo)文件就會(huì)有vtbl。所以一般忽略虛函數(shù)的inline指令。

            如果一個(gè)類具有虛函數(shù),那么這個(gè)類的每一個(gè)對(duì)象都會(huì)具有指向這個(gè)虛函數(shù)表的指針,這是一個(gè)隱藏?cái)?shù)據(jù)成員vptr~被編譯器加在某一個(gè)位置。

            此處第二個(gè)開(kāi)銷:你必須在每一個(gè)對(duì)象中存放一個(gè)額外的指針~

            如果對(duì)象很小這個(gè)開(kāi)銷就十分顯著~~因?yàn)楸壤髜

            此時(shí) void makeCall(c1* pc1){

               pc1->f1();

            }

            翻譯為 (*pc1->vptr[i])(pc1);

            根據(jù)vptr找到vtbl 這很簡(jiǎn)單,

            在vtbl找到調(diào)用函數(shù)對(duì)應(yīng)的函數(shù)指針,這個(gè)步驟也很簡(jiǎn)單,因?yàn)榫幾g器為虛函數(shù)表里的每一個(gè)函數(shù)設(shè)置了唯一的索引

            然后調(diào)用指針?biāo)赶虻暮瘮?shù)~

            這樣看來(lái),調(diào)用虛函數(shù)與普通函數(shù)調(diào)用的效率相差無(wú)幾,只多出幾個(gè)指令。

            虛函數(shù)真正的開(kāi)銷與內(nèi)聯(lián)函數(shù)有關(guān)~:在實(shí)際應(yīng)用中,虛函數(shù)不應(yīng)該被內(nèi)聯(lián),因?yàn)閮?nèi)聯(lián)意味著在編譯時(shí)刻用被調(diào)用函數(shù)的函數(shù)體來(lái)代替被調(diào)用函數(shù)。但是虛函數(shù)意味著運(yùn)行時(shí)刻決定調(diào)用哪個(gè)一函數(shù),so~~~虛函數(shù)付出的第三個(gè)代價(jià)啊:~不能內(nèi)聯(lián)(通過(guò)對(duì)象調(diào)用虛函數(shù)的時(shí)候,這些虛函數(shù)可以內(nèi)聯(lián),但是大多數(shù)虛函數(shù)通過(guò)指針或者以用來(lái)調(diào)用的)。

            ~多重繼承的情況

            多重繼承一般要求虛基類。沒(méi)有虛基類,如果一個(gè)派生類具有多個(gè)通向基類的繼承路徑,基類的數(shù)據(jù)成員會(huì)被復(fù)制到每一個(gè)繼承類對(duì)象里,繼承類與基類間的每一條路徑都有一個(gè)拷貝。

            有了虛基類,通常使用指向虛基類的指針作為避免重復(fù)的手段,這樣需要在對(duì)象內(nèi)部嵌入一個(gè)或者多個(gè)指針~也帶來(lái)了一定的開(kāi)銷~

            例如菱形繼承 :

            class A{};

            class B:virtual public A{};

            class C:virtual public A{};

            class D:public B,public C{};

            這里A是一個(gè)虛基類,因?yàn)锽和C虛擬繼承了他。

            對(duì)象 D 的布局:

            B data

            vptr

            pointer to virtual base class

            C data

            vptr

            pointer to virtual base class

            D data members

            A data members

            vptr

            上面四個(gè)類,只有三個(gè)vptr,因?yàn)锽和D可以共享一個(gè)vptr  (為啥?)

            現(xiàn)在我們已經(jīng)看到虛函數(shù)如何使對(duì)象變得更大,以及為何不能把它內(nèi)聯(lián)了~

            下面我們看看RTTI的開(kāi)銷 runtime type identifycation 所需要的開(kāi)銷

            通過(guò)rtti我們可以知道對(duì)象和類的有關(guān)信息,所以肯定在某個(gè)地方存儲(chǔ)了這些供我們查詢的信息,這些信息被存儲(chǔ)在type_info 類型的對(duì)象里,你可以通過(guò)typeid運(yùn)算符訪問(wèn)一個(gè)類的type_info對(duì)象。

            每個(gè)類僅僅需要一個(gè)RTTI的拷貝,規(guī)范上只保證提供哪些至少有一個(gè)虛函數(shù)的對(duì)象的準(zhǔn)確的動(dòng)態(tài)類型信息~

            why?和虛函數(shù)有啥關(guān)系~ 因?yàn)閞tti設(shè)計(jì)在vtbl里

            vtbl的下標(biāo)0包含指向type_info對(duì)象的指針。所以使用這種實(shí)現(xiàn)方法,消費(fèi)的空間是vtbl中占用一個(gè)額外的單元再加上存儲(chǔ)type_info對(duì)象所需要的空間。

             

            ------------------------罪惡的結(jié)束線 OVER~------------------------------------------

            日韩久久久久久中文人妻 | 伊人久久大香线蕉成人| 99久久做夜夜爱天天做精品| 久久免费看黄a级毛片| 久久久久亚洲av无码专区喷水| 精品国产VA久久久久久久冰| 欧美伊人久久大香线蕉综合69 | 久久精品国产精品亚洲精品| 久久久WWW成人| 久久精品国产亚洲AV麻豆网站| 亚洲国产精品久久久久婷婷软件| 久久男人中文字幕资源站| 久久久久无码精品国产不卡| 久久综合亚洲色HEZYO国产| 国产精品久久久久久吹潮| 亚洲欧洲中文日韩久久AV乱码| 久久精品国产亚洲AV大全| 无码人妻久久一区二区三区蜜桃| 欧美一区二区精品久久| 久久精品午夜一区二区福利| 一本色综合久久| 欧美日韩精品久久久久| 91精品国产综合久久香蕉| 国产精品一区二区久久| 亚洲精品乱码久久久久久蜜桃不卡 | 精品久久久久久国产| 色99久久久久高潮综合影院 | 久久久久人妻一区精品| 色噜噜狠狠先锋影音久久| 1000部精品久久久久久久久| 亚洲va久久久噜噜噜久久男同| 久久受www免费人成_看片中文| 国内精品久久久久影院网站| 亚洲伊人久久大香线蕉苏妲己| 2020久久精品国产免费| 久久久久久狠狠丁香| 99精品久久精品一区二区| 久久青草国产精品一区| 免费精品99久久国产综合精品| 久久精品草草草| 精品久久久久中文字|