日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 人文社科 > 生活经验 >内容正文

生活经验

提高C++性能的编程技术笔记:内联+测试代码

發(fā)布時(shí)間:2023/11/27 生活经验 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 提高C++性能的编程技术笔记:内联+测试代码 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

內(nèi)聯(lián)類似于宏,在調(diào)用方法內(nèi)部展開被調(diào)用方法,以此來(lái)代替方法的調(diào)用。一般來(lái)說(shuō)表達(dá)內(nèi)聯(lián)意圖的方式有兩種:一種是在定義方法時(shí)添加內(nèi)聯(lián)保留字的前綴;另一種是在類的頭部聲明中定義方法。

雖然內(nèi)聯(lián)方法的調(diào)用方式和普通方法相同,但其編譯過(guò)程卻相差甚遠(yuǎn)。由于內(nèi)聯(lián)方法的代碼必須內(nèi)聯(lián)展開,這就要求調(diào)用內(nèi)聯(lián)方法的代碼段必須有權(quán)訪問(wèn)該內(nèi)聯(lián)方法的定義。而內(nèi)聯(lián)方法的定義需要整合到其調(diào)用方法之中,這就使得任何針對(duì)內(nèi)聯(lián)方法的更改,都將引起所有調(diào)用該方法模塊的重新編譯。所以,內(nèi)聯(lián)在顯著提升性能的同時(shí),也增加了編譯時(shí)間。編譯時(shí)間的增加有時(shí)是適度的,但有時(shí)卻極大,并且在大多數(shù)極端情況下,對(duì)內(nèi)聯(lián)方法的一處修改可能會(huì)要求整體程序重新進(jìn)行編譯。因此,將內(nèi)聯(lián)過(guò)程擱置到代碼開發(fā)階段后期是明智的做法。

?從邏輯上說(shuō),編譯器將方法內(nèi)聯(lián)化的步驟如下:首先將待內(nèi)聯(lián)方法的連續(xù)代碼塊復(fù)制到調(diào)用方法中的調(diào)用點(diǎn)處。然后在塊中為所有內(nèi)聯(lián)方法的局部變量分配內(nèi)存。之后將內(nèi)聯(lián)方法的輸入?yún)?shù)和返回值映射到調(diào)用方法的局部變量空間內(nèi)。最后,如果內(nèi)聯(lián)方法有多個(gè)返回點(diǎn),將其轉(zhuǎn)變?yōu)閮?nèi)聯(lián)代碼塊末尾的分支。經(jīng)過(guò)這樣的處理即可消除所有與調(diào)用相關(guān)的痕跡以及性能損失。避免方法調(diào)用僅僅是內(nèi)聯(lián)可提升的性能空間的一半。調(diào)用間(cross-call)優(yōu)化是內(nèi)聯(lián)可提升的性能空間的另外一半。優(yōu)秀的、經(jīng)過(guò)優(yōu)化的編譯器可以使內(nèi)聯(lián)方法的邊界痕跡難以區(qū)分。方法中大量的甚至是所有的代碼經(jīng)過(guò)優(yōu)化后都將不復(fù)存在,因?yàn)榫幾g器可能會(huì)對(duì)方法中大部分代碼進(jìn)行重新排列。因此,盡管在邏輯上可以將方法內(nèi)聯(lián)化看作是對(duì)一定內(nèi)聚度的維持,不過(guò)編譯器并未強(qiáng)制執(zhí)行這種優(yōu)化措施,這也是內(nèi)聯(lián)的優(yōu)點(diǎn)之一。

大部分系統(tǒng)都有3或4個(gè)”內(nèi)務(wù)處理”寄存器:指令指針(Instruction Pointer,常被稱為程序計(jì)數(shù)器----Program Counter,但其功能并非對(duì)程序進(jìn)行計(jì)數(shù)),鏈接寄存器(Link Register),棧指針(Stack Pointer), 幀指針(Frame Pointer)以及自變量指針(Argument Pointer),分別可以記作IP、LR、SP、FP以及AP。

指令指針(IP):存放下一條將要執(zhí)行的指令地址。調(diào)用方法時(shí),程序要跳轉(zhuǎn)到被調(diào)用方法的指令并修改IP。但不能只是簡(jiǎn)單地重新IP。重寫前必須先保存其舊值,否則無(wú)法返回至原調(diào)用方法。

鏈接寄存器(LR):存儲(chǔ)某一方法的IP的地址,該方法對(duì)當(dāng)前方法進(jìn)行了調(diào)用。這個(gè)地址就是方法執(zhí)行完畢后返回的地方。LR通常和體系結(jié)構(gòu)調(diào)用指令的操作綁定在一起,執(zhí)行調(diào)用操作時(shí)其值會(huì)被自動(dòng)設(shè)定。LR是單個(gè)寄存器而非多寄存器集合。因此,如果某方法調(diào)用了其它方法,則必須保存LR的值以防止其被重寫,因?yàn)檎{(diào)用者標(biāo)識(shí)消失后,就很難有效地從調(diào)用中返回。在某些體系結(jié)構(gòu)中,LR的功能是通過(guò)自動(dòng)或顯示地調(diào)用方法IP壓入程序進(jìn)程堆棧來(lái)實(shí)現(xiàn)的,因此這些體系結(jié)構(gòu)不存在明確的LR。

棧指針(SP):方法的局部(自)變量是在進(jìn)程堆棧上分配的。而SP的功能就是跟蹤記錄堆棧的使用情況。調(diào)用操作會(huì)消耗堆棧空間,返回操作則會(huì)釋放之前分配的堆棧空間。類似于調(diào)用者IP和LR,調(diào)用返回之后,必須根據(jù)傳遞到堆棧的參數(shù)來(lái)進(jìn)行可能的調(diào)整以恢復(fù)堆棧。這就意味著SP也必須被保存為方法調(diào)用的一部分。

自變量指針(AP)和幀指針(FP)的存在隨系統(tǒng)而異。某些體系結(jié)構(gòu)不包含這兩個(gè)寄存器,某些僅包含一個(gè),而另外一些則兩者兼?zhèn)?。FP的作用是標(biāo)識(shí)堆棧中兩個(gè)區(qū)域的邊界:第一個(gè)區(qū)域供調(diào)用方法用來(lái)保存需要記錄狀態(tài)的寄存器;第二個(gè)區(qū)域?yàn)楸徽{(diào)用方法的自變量分配內(nèi)存。在方法執(zhí)行期間SP一般會(huì)頻繁地變化。FP通常被當(dāng)成方法中局部變量的固定引用指針。

良好的調(diào)用性能要求系統(tǒng)只保存方法用到的寄存器。每次調(diào)用都保存全部寄存器是無(wú)謂的浪費(fèi),但是只保存部分寄存器會(huì)導(dǎo)致在傳給方法的參數(shù)和分配給方法自變量之間產(chǎn)生潛在的內(nèi)存分配。如果數(shù)量可變的寄存器存儲(chǔ)和某個(gè)給定的方法調(diào)用相關(guān)聯(lián)(也即寄存器值存儲(chǔ)的數(shù)量依賴于調(diào)用方法的狀態(tài)),就需要用AP指出傳給方法的參數(shù)在堆棧中的位置。

使用寄存器的典型調(diào)用順序如下:

(1). 調(diào)用方法整理需要傳給被調(diào)用方法的參數(shù)。此步驟通常意味著要將參數(shù)壓棧,壓棧時(shí)一般采用倒序。所有參數(shù)入棧后,SP將指向第一個(gè)參數(shù)。

(2). 把將要返回的指令地址壓棧,然后調(diào)用指令跳轉(zhuǎn)到被調(diào)用方法的第一條指令處。

(3). 由被調(diào)用方法在堆棧中保存調(diào)用方法的SP、AP以及FP,并調(diào)整每個(gè)”管家”寄存器以反映被調(diào)用方法的上下文環(huán)境。

(4). 同時(shí),被調(diào)用方法保存(壓入堆棧)其將會(huì)用到的所有其它寄存器(此步驟是必要的,以便被調(diào)用方法返回后不會(huì)中斷調(diào)用方法的上下文環(huán)境,通常要保存另外的3或4個(gè)寄存器)。

清除調(diào)用的典型返回序列如下:

(1). 如果方法有返回值,則通常將該返回值存儲(chǔ)到寄存器0(有時(shí)是寄存器1)中。這就意味著寄存器0和寄存器1必須是暫時(shí)性寄存器(此類寄存器的存儲(chǔ)和恢復(fù)不是方法調(diào)用和返回的一部分)。通過(guò)寄存器使得返回操作的堆棧清理工作更加容易。

(2). 將由于方法調(diào)用而保存的寄存器從堆棧中恢復(fù)至其初始位置。

(3). 將保存的調(diào)用者的FP和AP寄存器值從堆棧中恢復(fù)到其相應(yīng)的位置。

(4). 修改SP使其指向?qū)⒎椒ǖ谝粋€(gè)參數(shù)壓棧前的位置。

(5). 從堆棧中找到返回地址并將其存入IP,強(qiáng)制返回至調(diào)用者中緊接調(diào)用點(diǎn)的位置。

簡(jiǎn)單算一算單次方法調(diào)用過(guò)程中的數(shù)據(jù)移動(dòng)次數(shù)可以看出,6~8個(gè)寄存器(4個(gè)寄存器用于維護(hù)現(xiàn)場(chǎng),2~4個(gè)供方法使用)被保存過(guò),其中4個(gè)稍后被修改過(guò)。通常情況下這些操作至少需要12個(gè)時(shí)鐘周期(實(shí)際中數(shù)據(jù)移入/移出內(nèi)存很少只花費(fèi)單個(gè)時(shí)鐘周期),有時(shí)甚至?xí)亩噙_(dá)40個(gè)時(shí)鐘周期。因此,就機(jī)器時(shí)鐘周期方面的花費(fèi)而言,與方法調(diào)用相關(guān)的操作的代價(jià)非常昂貴。不幸的是,以上所述只是所有開銷的一半。方法返回時(shí),為調(diào)用過(guò)程所做的工作必須全部撤銷。之前保存的值必須從堆棧中恢復(fù),機(jī)器狀態(tài)也必須恢復(fù)到和調(diào)用前類似。這就意味著單次方法調(diào)用通常需要消耗25~100個(gè)時(shí)鐘周期,有時(shí)這甚至僅是保守估計(jì)。之所以說(shuō)是保守估計(jì),部分原因與參數(shù)的準(zhǔn)備及獲取有關(guān)。被壓棧的參數(shù)作為調(diào)用開始階段的一部分,通常直接映射到被調(diào)用方法的內(nèi)存映像中。對(duì)于引用來(lái)說(shuō)始終如此,而對(duì)于指針和對(duì)象則是有時(shí)如此。因而會(huì)產(chǎn)生額外的調(diào)用開銷,這種開銷與調(diào)用前參數(shù)的壓棧以及被調(diào)用方法將其從堆棧中讀回的操作相關(guān)。某些情況下可以通過(guò)寄存器進(jìn)行參數(shù)傳遞,雖然這種機(jī)制可以提供很好的性能特性(盡管不無(wú)代價(jià)),但是公認(rèn)的機(jī)制還是使用內(nèi)存來(lái)傳遞參數(shù)。

如果方法有返回值,特別當(dāng)其返回值是一個(gè)對(duì)象時(shí),被調(diào)用方法將對(duì)象復(fù)制到調(diào)用方法為返回值預(yù)留的存儲(chǔ)空間中也是一筆開銷。對(duì)于較大的對(duì)象而言,這筆額外開銷將更加客觀,尤其是使用復(fù)雜的拷貝構(gòu)造函數(shù)執(zhí)行該任務(wù)時(shí)(這種會(huì)產(chǎn)生兩份調(diào)用/返回開銷:一份開銷是因?yàn)閷?duì)方法的顯示調(diào)用,另一份則是拷貝構(gòu)造函數(shù)返回一個(gè)對(duì)象時(shí)產(chǎn)生的開銷)。如果將所有調(diào)用者/被調(diào)用者的通信因素和系統(tǒng)維護(hù)因素也考慮在內(nèi),方法調(diào)用的代價(jià)大約為25~250個(gè)時(shí)鐘周期。通常被調(diào)用方法越大,所產(chǎn)生的開銷也越大,取決于保存恢復(fù)所有寄存器、傳遞大量參數(shù)、調(diào)用自定義方法以構(gòu)造返回值等操作的最大開銷。

使用異常處理可以顯著降低內(nèi)聯(lián)返回值優(yōu)化的性能。從邏輯上來(lái)說(shuō),返回值的復(fù)制工作是作為被調(diào)用方法返回過(guò)程一部分的、由拷貝構(gòu)造函數(shù)執(zhí)行的原子操作。這就意味著如果返回前有異常被拋出,則返回值將不會(huì)返回,而且存放方法返回值的變量也不會(huì)改變,從而導(dǎo)致異常發(fā)生時(shí),必須為返回值使用復(fù)制語(yǔ)義。這在某些情況下也成為避免使用異常處理的正當(dāng)理由。理想情況下,為達(dá)到優(yōu)化目的,如果存在一些語(yǔ)法標(biāo)記以允許異常發(fā)生時(shí)對(duì)返回值進(jìn)行優(yōu)化,將會(huì)帶來(lái)極大便利。某些針對(duì)異常發(fā)生時(shí)的返回值優(yōu)化已經(jīng)可以實(shí)現(xiàn)。例如,如果返回值變量的作用域和內(nèi)聯(lián)方法處在同一try代碼塊內(nèi),則返回值可被優(yōu)化。不幸的是,盡管這種情形在大多數(shù)情況下可以輕易確定,但其要求的調(diào)用間優(yōu)化往往代價(jià)高昂且實(shí)現(xiàn)起來(lái)十分復(fù)雜。

內(nèi)聯(lián)的另一個(gè)好處是無(wú)須跳轉(zhuǎn)執(zhí)行被調(diào)用方法。跳轉(zhuǎn),即便是無(wú)條件跳轉(zhuǎn),都會(huì)對(duì)現(xiàn)代處理器的性能產(chǎn)生負(fù)面影響。頻繁跳轉(zhuǎn)會(huì)造成執(zhí)行流水線遲滯,這是因?yàn)轭A(yù)取緩存中沒(méi)有需要執(zhí)行的指令。跳轉(zhuǎn)還需要運(yùn)算單元為其確定跳轉(zhuǎn)目標(biāo)地址,使指令直到跳轉(zhuǎn)地址可知方可執(zhí)行。流水線的延遲意味著處理器將會(huì)因?yàn)榇罅繒r(shí)間被用于重定向指令流而處于閑置狀態(tài)。每次方法調(diào)用時(shí)這種情況都會(huì)發(fā)生兩次----方法被調(diào)用階段和返回階段。

在某些情況下,方法調(diào)用最大的代價(jià)就是無(wú)法對(duì)跨越方法邊界的代碼進(jìn)行優(yōu)化。

內(nèi)聯(lián)可能是C++中可用的最有效的性能提升機(jī)制。通過(guò)內(nèi)聯(lián)的方式,無(wú)須進(jìn)行任何重寫即可使大型的系統(tǒng)迅速提升性能。

內(nèi)聯(lián)是一種由編譯器/配置器/優(yōu)化器執(zhí)行的、基于編譯和配置的優(yōu)化操作。

保留字”inline”僅表示對(duì)編譯器的一種建議。它告訴編譯器,將方法代碼內(nèi)聯(lián)展開而不是調(diào)用可以獲得更佳性能。但是編譯器沒(méi)有義務(wù)答應(yīng)內(nèi)聯(lián)請(qǐng)求。因此編譯器可以根據(jù)自己的意愿或者能力來(lái)選擇是否進(jìn)行內(nèi)聯(lián)。這就意味著即使沒(méi)有被明確告知需要內(nèi)聯(lián)(對(duì)低價(jià)值方法編譯器會(huì)自動(dòng)內(nèi)聯(lián),這常常是優(yōu)化的副作用)編譯器也會(huì)這樣去做,或者即便被明確告知需要內(nèi)聯(lián)卻不進(jìn)行內(nèi)聯(lián)。

內(nèi)聯(lián)還會(huì)引起一些值得注意的副作用:從邏輯上來(lái)說(shuō),雖然經(jīng)常被存放于單獨(dú)的.inl文件中,但其實(shí)內(nèi)聯(lián)方法的定義應(yīng)為類頭文件的一部分。頭文件及其邏輯上包含的.inl文件隨后被用到它們的.c或.cpp文件包含。源文件被編譯為目標(biāo)文件后,就不需要在目標(biāo)文件做任何標(biāo)示以說(shuō)明目標(biāo)文件包含哪些內(nèi)聯(lián)方法了。也就是說(shuō),通常情況下,目標(biāo)文件已完全解析了內(nèi)聯(lián)方法且不需要再對(duì)其存在性進(jìn)行保存(不存在鏈接需求)。因此,盡管C++語(yǔ)言明文禁止,但是源文件仍然可以和內(nèi)聯(lián)方法的定義一起編譯,而另一源文件也可以和另一版本不同但方法相同的文件一起編譯。

如果編譯器足夠完善,許多對(duì)虛方法的調(diào)用是可以內(nèi)聯(lián)化的。因此,如果配置文件指出某些虛方法需要占用程序過(guò)多的運(yùn)行時(shí)間,則可通過(guò)將部分方法調(diào)用內(nèi)聯(lián)化來(lái)挽回一些開銷。這也說(shuō)明如果編譯器有能力并且選擇了將虛方法內(nèi)聯(lián)化,那么幾乎可以保證一定會(huì)有一些針對(duì)同一方法的內(nèi)聯(lián)調(diào)用實(shí)例以及虛方法調(diào)用實(shí)例。

內(nèi)聯(lián)就是用方法的代碼來(lái)替換對(duì)方法的調(diào)用。

內(nèi)聯(lián)通過(guò)消除調(diào)用開銷來(lái)提升性能,并且允許進(jìn)行調(diào)用間優(yōu)化

內(nèi)聯(lián)的主要作用是對(duì)運(yùn)行時(shí)間進(jìn)行優(yōu)化,當(dāng)然它也可以使可執(zhí)行映像變得更小

調(diào)用間(cross-call)優(yōu)化:面向某一方法的調(diào)用過(guò)程,基于對(duì)上下文場(chǎng)景更加全面的理解,使得編譯器在源代碼層面及機(jī)器代碼層面對(duì)方法進(jìn)行優(yōu)化。這種優(yōu)化的一般形式為:在編譯期間進(jìn)行一部分預(yù)處理,從而避免在運(yùn)行時(shí)重復(fù)類似的過(guò)程。內(nèi)聯(lián)的這類優(yōu)化應(yīng)是編譯器的職責(zé),而不是程序員的

與避免方法調(diào)用這種簡(jiǎn)單的做法相比,調(diào)用間代碼優(yōu)化更可能獲得巨大的性能提升。但從另一個(gè)角度來(lái)看,避免方法調(diào)用獲得的性能提升是確定的,雖然有時(shí)效果并不盡如人意,但這種做法具有普遍性。代碼優(yōu)化與編譯器密切相關(guān),高層次的優(yōu)化方法將會(huì)使編譯過(guò)程變得漫長(zhǎng),實(shí)際上有時(shí)還會(huì)打亂代碼。

何時(shí)避免內(nèi)聯(lián):當(dāng)程序中所有能夠內(nèi)聯(lián)的方法都進(jìn)行內(nèi)聯(lián),代碼膨脹將不可估量,這將對(duì)性能產(chǎn)生巨大的二次負(fù)面影響,如緩存命中問(wèn)題和頁(yè)面錯(cuò)誤,而這些將令我們的工作得不償失。另一方面,濫用的內(nèi)聯(lián)程序?qū)?zhí)行較少的指令,但會(huì)耗費(fèi)較多的時(shí)鐘周期。內(nèi)聯(lián)的濫用導(dǎo)致的緩存錯(cuò)誤會(huì)使性能銳減。代碼膨脹所帶來(lái)的副作用可能是無(wú)法承受的。

內(nèi)聯(lián)所引發(fā)的代碼膨脹現(xiàn)象有時(shí)會(huì)導(dǎo)致另一類退化特征。將某個(gè)方法內(nèi)聯(lián)可能會(huì)導(dǎo)致指數(shù)級(jí)的代碼膨脹。這種現(xiàn)象通常會(huì)發(fā)生在相對(duì)大規(guī)模的例程互相內(nèi)聯(lián)的情況下。

內(nèi)聯(lián)方法不僅在實(shí)現(xiàn)層面會(huì)產(chǎn)生編譯依賴,在接口層面亦然。正因如此,對(duì)于在程序開發(fā)階段經(jīng)常發(fā)生變動(dòng)的方法,不應(yīng)將其列入可內(nèi)聯(lián)的范疇??梢杂靡粭l規(guī)則來(lái)總結(jié):能夠縮減代碼大小的內(nèi)聯(lián)都是可取的,而任何顯著增大代碼大小的內(nèi)聯(lián)都是不可取的。第二條有用的規(guī)則:如果方法的實(shí)現(xiàn)是易變的,則不應(yīng)將其內(nèi)聯(lián)。

通常應(yīng)避免遞歸方法內(nèi)聯(lián)。

從邏輯上講,內(nèi)聯(lián)方法應(yīng)定義在其類的頭文件中。這對(duì)于使用內(nèi)聯(lián)方法代碼體的那些調(diào)用者是必要的。不幸的是,一旦內(nèi)聯(lián)方法的內(nèi)容有所改變,都將導(dǎo)致用到內(nèi)聯(lián)方法的相關(guān)模塊重新編譯----是重新編譯而不只是重新連接。對(duì)于大規(guī)模程序來(lái)說(shuō),由于每次編譯都會(huì)帶來(lái)額外的時(shí)間消耗,這無(wú)疑增加了程序的開發(fā)時(shí)間。

針對(duì)內(nèi)聯(lián)方法的調(diào)試較為復(fù)雜,因?yàn)閱蝹€(gè)斷點(diǎn)無(wú)法跟蹤內(nèi)聯(lián)方法的入口和出口。

內(nèi)聯(lián)方法通常并不出現(xiàn)在程序的配置表中(配置表基于某些示例程序的模板,顯示程序的執(zhí)行行為)。配置表有時(shí)無(wú)法察覺(jué)對(duì)內(nèi)聯(lián)方法的”調(diào)用”。

基于配置的內(nèi)聯(lián):配置是尋求適合內(nèi)聯(lián)的方法的最佳手段,尤其當(dāng)我們擁有可以用來(lái)產(chǎn)生配置數(shù)據(jù)的代表性數(shù)據(jù)樣本時(shí),配置的優(yōu)勢(shì)將會(huì)更加明顯。配置(Profiling)是一種依靠配置工具(軟件包)的性能測(cè)試技術(shù),通過(guò)為程序生成工具代碼(插入測(cè)試代碼),在程序樣本執(zhí)行期間使性能具備某些特征。生成配置的數(shù)據(jù)樣本質(zhì)量直接決定了配置文件的質(zhì)量。配置文件的形式和大小不拘一格,其可能的輸出范圍也是千差萬(wàn)別,然而一般來(lái)說(shuō),所有的配置文件都應(yīng)至少提供如:哪些方法正在執(zhí)行,這些方法多久被調(diào)用一次等相關(guān)信息。

配置文件要同時(shí)兼顧指令數(shù)的計(jì)算和時(shí)間的度量。時(shí)間是一種更為精確的度量指標(biāo),但指令數(shù)的生成更為簡(jiǎn)單,并且它所提供的數(shù)據(jù)可以用做內(nèi)聯(lián)決策的依據(jù)。

編譯器通常禁止內(nèi)聯(lián)復(fù)雜的方法。

內(nèi)聯(lián)規(guī)則

(1).唯一化(singleton)方法:是指方法在程序中的調(diào)用點(diǎn)是唯一的,而它并不代表方法在程序執(zhí)行過(guò)程中只被調(diào)用一次。某個(gè)方法或許會(huì)出現(xiàn)在循環(huán)中,被成千上萬(wàn)次調(diào)用,但是只要其在程序中的調(diào)用點(diǎn)唯一,我們就稱其為唯一化方法。唯一化方法具備與生俱來(lái)的內(nèi)聯(lián)特性。唯一的調(diào)用點(diǎn)意味著我們不需要考慮方法的大小和調(diào)用頻率,對(duì)于內(nèi)聯(lián)后的唯一化方法,其代碼將比原先更小,運(yùn)行更快?;蛟S由此獲得的性能提升并不明顯,但是有一點(diǎn)要清楚,我們?cè)趦?nèi)聯(lián)方面付出的努力并不總是能帶來(lái)性能上的提升。一般來(lái)說(shuō),唯一化方法的鑒別較為困難,方法的唯一化往往是臨時(shí)的,并且與環(huán)境相關(guān),而有時(shí),唯一化是設(shè)計(jì)的產(chǎn)物。

(2).精簡(jiǎn)化(trivial)方法:都是一些小型方法,通常包含4條以下的源代碼級(jí)語(yǔ)句,這些語(yǔ)句被編譯后將形成10條以下的匯編指令。這些方法包含的語(yǔ)句很少,從而產(chǎn)生代碼膨脹的可能性幾乎為零。將小規(guī)模的精簡(jiǎn)化方法內(nèi)聯(lián)實(shí)際上將減少代碼量,將較大規(guī)模的精簡(jiǎn)化方法內(nèi)聯(lián)可能會(huì)使總體代碼量略微增加??偟膩?lái)說(shuō),將精簡(jiǎn)化方法進(jìn)行內(nèi)聯(lián)的最終效果不會(huì)影響代碼大小。

直接量參數(shù)與內(nèi)聯(lián)結(jié)合使用,為編譯器性能的大幅提升開辟了更為廣闊的空間。

使用內(nèi)聯(lián)有時(shí)會(huì)適得其反,尤其是濫用的情況下,內(nèi)聯(lián)可能會(huì)使代碼量變大,而代碼量增多后會(huì)較原先出現(xiàn)更多的緩存失敗和頁(yè)面錯(cuò)誤

非精簡(jiǎn)方法的內(nèi)聯(lián)決策應(yīng)根據(jù)樣本執(zhí)行的配置文件來(lái)制定,不能主觀臆斷。

對(duì)于那些調(diào)用頻率高的方法,如果其靜態(tài)尺寸較大,而動(dòng)態(tài)尺寸較小,可以考慮將其重寫,從而抽取其核心的動(dòng)態(tài)特性,并將動(dòng)態(tài)組件內(nèi)聯(lián)。

精簡(jiǎn)化與唯一化方法總是可以被內(nèi)聯(lián)

條件內(nèi)聯(lián):編譯、調(diào)試和配置等過(guò)程與內(nèi)聯(lián)是有一些沖突的,做這些工作時(shí),都希望將內(nèi)聯(lián)決策推遲到開發(fā)周期的后期,在大部分調(diào)試工作完成之后進(jìn)行。預(yù)處理可以協(xié)助我們實(shí)現(xiàn)在內(nèi)聯(lián)與外聯(lián)之間的輕松轉(zhuǎn)移。這項(xiàng)技術(shù)的基本思路是利用編譯器行參數(shù)向編譯器傳遞一個(gè)宏定義。輸入?yún)?shù)用來(lái)定義名為INLINE的宏,也可以忽略這個(gè)參數(shù)而不定義INLINE。這種技術(shù)基于對(duì)兩種定義的劃分,即需要內(nèi)聯(lián)的方法及需要外聯(lián)的方法。外聯(lián)方法包含于標(biāo)準(zhǔn)的.c文件中,需要內(nèi)聯(lián)的方法放置在.inl文件中。如果對(duì).inl文件中的方法內(nèi)聯(lián),可以在編譯命令中使用-D選項(xiàng)來(lái)定義INLINE宏。

選擇性內(nèi)聯(lián):內(nèi)聯(lián)機(jī)制的語(yǔ)法和機(jī)動(dòng)性是C++最嚴(yán)重的缺陷之一。盡管通常情況下它很有用,但卻存在一個(gè)令人頭疼的缺陷,即它沒(méi)有針對(duì)選擇性內(nèi)聯(lián)的機(jī)制;所謂選擇性內(nèi)聯(lián),是指在某些場(chǎng)合下對(duì)方法進(jìn)行內(nèi)聯(lián)而在另外一些場(chǎng)合則不然。這種缺陷使得內(nèi)聯(lián)決策成為非全則無(wú)的選擇,從而忽略了快速路徑優(yōu)化的真實(shí)情況。

遞歸內(nèi)聯(lián):直接遞歸方法是無(wú)法內(nèi)聯(lián)的。尾部遞歸是遞歸方法中的一種。它表現(xiàn)為方法在達(dá)到它的基線條件之前一直遞歸下降,當(dāng)?shù)竭_(dá)基線條件后執(zhí)行一些操作并終止方法,可能還會(huì)返回一個(gè)值。典型的二叉樹搜索就是一個(gè)很好的尾部遞歸方法的例子。

對(duì)靜態(tài)局部變量進(jìn)行內(nèi)聯(lián):對(duì)基于編譯器的內(nèi)聯(lián)解決方案來(lái)說(shuō),局部靜態(tài)變量可能會(huì)造成很大問(wèn)題。這是因?yàn)橐恍┚幾g器會(huì)拒絕內(nèi)聯(lián)任何包含靜態(tài)變量聲明的方法。還有一些編譯器允許內(nèi)聯(lián)靜態(tài)變量,但是在運(yùn)行時(shí)它們會(huì)錯(cuò)誤地為這些內(nèi)聯(lián)變量創(chuàng)建多個(gè)實(shí)例。

與體系結(jié)構(gòu)有關(guān)的注意事項(xiàng):對(duì)于各種不同體系結(jié)構(gòu)來(lái)說(shuō),它們的調(diào)用/返回性能不盡相同。

內(nèi)聯(lián)可以改善性能。目標(biāo)是找到程序的快速路徑,然后內(nèi)聯(lián)它,盡管內(nèi)聯(lián)這個(gè)路徑可能要費(fèi)點(diǎn)工夫。

條件內(nèi)聯(lián)可以阻止內(nèi)聯(lián)的發(fā)生。這樣就減少了編譯時(shí)間,同時(shí)也簡(jiǎn)化了開發(fā)前期的調(diào)試工作。

選擇性內(nèi)聯(lián)是一種只在某些地方內(nèi)聯(lián)方法的技術(shù)。在對(duì)方法進(jìn)行內(nèi)聯(lián)時(shí),為了抵消可能的代碼尺寸膨脹的影響,選擇性內(nèi)聯(lián)只在對(duì)性能有重大影響的路徑上對(duì)方法調(diào)用進(jìn)行內(nèi)聯(lián)。

遞歸內(nèi)聯(lián)是一種讓人感覺(jué)別扭,但對(duì)于改善遞歸方法性能卻很有效的技術(shù)。

內(nèi)聯(lián)的目標(biāo)是消除調(diào)用開銷。在使用內(nèi)聯(lián)之前須先弄清當(dāng)前系統(tǒng)中真正的調(diào)用代價(jià)。

以下是測(cè)試代碼(inline.cpp):

#include "inline.hpp"
#include <iostream>
#include <chrono>
#include <string>
#include <random>
#include <cmath>namespace inline_ {// reference: 《提高C++性能的編程技術(shù)》:第八、九、十章:內(nèi)聯(lián)
//
void generator_random_number(double* data, int length, double a, double b)
{//std::random_device rd; std::mt19937 generator(rd()); // 每次產(chǎn)生不固定的不同的值std::default_random_engine generator; // 每次產(chǎn)生固定的不同的值std::uniform_real_distribution<double> distribution(a, b);for (int i = 0; i < length; ++i) {data[i] = distribution(generator);}
}double calc1(double a, double b) // 非內(nèi)聯(lián)
{return (a+b);
}inline double calc2(double a, double b) // 內(nèi)聯(lián)
{return (a+b);
}int test_inline_1()
{using namespace std::chrono;high_resolution_clock::time_point time_start, time_end;const int count{2000};const int cycle_number1{count}, cycle_number2{count}, cycle_number3{count};double x[count], y[count], z1[count], z2[count];generator_random_number(x, count, -1000., 1000.);generator_random_number(y, count, -10000, 10000.);{ // 測(cè)試簡(jiǎn)單的非內(nèi)聯(lián)函數(shù)調(diào)用執(zhí)行時(shí)間time_start = high_resolution_clock::now();for (int j = 0; j < cycle_number1; ++j) {for (int i = 0; i < cycle_number2; ++i) {for (int k = 0; k < cycle_number3; ++k) {z1[i] = calc1(x[k], y[k]);}}}time_end = high_resolution_clock::now();fprintf(stdout, "z1: %f, %f, %f, no inline calc time spent: %f seconds\n",z1[0], z1[1], z1[2], (duration_cast<duration<double>>(time_end - time_start)).count());
}{ // 測(cè)試簡(jiǎn)單的內(nèi)聯(lián)函數(shù)調(diào)用執(zhí)行時(shí)間time_start = high_resolution_clock::now();for (int j = 0; j < cycle_number1; ++j) {for (int i = 0; i < cycle_number2; ++i) {for (int k = 0; k < cycle_number3; ++k) {z2[i] = calc2(x[k], y[k]);}}}time_end = high_resolution_clock::now();fprintf(stdout, "z2: %f, %f, %f, inline calc time spent: %f seconds\n",z2[0], z2[1], z2[2], (duration_cast<duration<double>>(time_end - time_start)).count());
}return 0;
}} // namespace inline_

執(zhí)行結(jié)果如下:

GitHub:?https://github.com/fengbingchun/Messy_Test?

總結(jié)

以上是生活随笔為你收集整理的提高C++性能的编程技术笔记:内联+测试代码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。