簡介
返回值最佳化,是一種屬於
編譯器的技術,它通過轉換原始碼和對象的創建來加快原始碼的執行速度。當函式需要返回一個對象的時候,如果自己創建一個臨時對象用戶返回,那么這個臨時對象會消耗一個構造函式(Constructor)的調用、一個複製構造函式的調用(Copy Constructor)以及一個
析構函式(Destructor)的調用的代價。而如果稍微做一點最佳化,就可以將成本降低到一個構造函式的代價,這樣就省去了一次拷貝構造函式的調用和依次析構函式的調用。
構造函式與析構函式
構造函式,是一種特殊的方法。主要用來在創建對象時初始化對象,即為對象成員變數賦初始值,總與new運算符一起使用在創建對象的語句中。特別的一個類可以有多個構造函式,可根據其參數個數的不同或參數類型的不同來區分它們即構造函式的重載。多數程式語言允許構造函式重載,一個類被允許擁有多個接受不同參數種類的構造函式同時存在。一些程式語言允許某些特殊種類的構造函式。使用單個類來具體地建立和返回新實例的構造函式,時常被抽象為工廠方法 - 一種同樣用來建立新對象,但會同時使用多個類,或者一些諸如對象池的分配方案來完成這一過程的子程式。構造函式類型有參數化構造函式、默認構造函式。
接收參數的構造函式被稱為參數化構造函式。參數的數量可以大於或等於一。如果在編寫一個可實例化的類時沒有專門編寫構造函式,多數程式語言會自動生成預設構造函式。
預設構造函式的特性依不同語言而定。某些情況下它會將所有的實例變數同時初始化到0,或者任何其他別的值;某些預設構造函式什麼也不會做。某些語言 (Java, C#, VB .NET) 會預設構造由該類類型定義的數組,使其充滿空值引用。沒有空值引用的語言一般會禁止預設構造包含不可預設構造對象的數組,或者要求在建立時專門初始化這些數值。
析構函式(destructor) 與構造函式相反,當對象結束其生命周期時(例如對象所在的函式已調用完畢),系統自動執行析構函式。析構函式往往用來做“清理善後” 的工作(例如在建立對象時用new開闢了一片記憶體空間,delete會自動調用析構函式後釋放記憶體)。對於構造函式應注意:不能在結構中定義析構函式、只能對類使用析構函式、一個類只能有一個析構函式、無法繼承或重載析構函式、無法調用析構函式,它們是被自動調用的,析構函式既沒有修飾符,也沒有參數。
以C++語言為例:析構函式名也應與類名相同,只是在函式名前面加一個位取反符~,例如~stud( ),以區別於構造函式。它不能帶任何參數,也沒有返回值(包括void類型)。只能有一個析構函式,不能重載。如果用戶沒有編寫析構函式,編譯系統會自動生成一個預設的析構函式(即使自定義了析構函式,編譯器也總是會為我們合成一個析構函式,並且如果自定義了析構函式,編譯器在執行時會先調用自定義的析構函式再調用合成的析構函式),它也不進行任何操作。所以許多簡單的類中沒有用顯式的析構函式。
最佳化示例
對於函式返回類對象,一種實現辦法是在函式調用語句前在stack frame上聲明一個隱藏對象,把該對象的地址隱蔽傳入被調用函式,函式的返回對象直接構造或者複製構造到該地址上。
struct Data { char bytes[16]; };Data f() { Data result = {}; // generate result return result;}int main() { Data d = f();}
可能產生的代碼如下:
struct Data { char bytes[16]; };Data * f(Data * _hiddenAddress) { Data result = {}; // copy result into hidden object *_hiddenAddress = result; return _hiddenAddress;}int main() { Data _hidden; // create hidden object Data d = *f(&_hidden); // copy the result into d}
這引起了Data對象被複製兩次。
另一種技術是命名返回值最佳化(Named return value optimization,NRVO)。[5]NRVO去除了基於棧的返回值的構造與析構。雖然這會導致最佳化與未最佳化的程式的不同行為。
struct Data { char bytes[16]; };void f(Data *p) { // generate result directly in *p}int main() { Data d; f(&d);}
大部分C++編譯器均支持返回值最佳化。在某些環境下,編譯器不能執行此最佳化。一個常見情形是當函式依據執行路徑返回不同的命名對象,或者命名對象在asm內聯塊中被使用:
#include <iostream>struct C { C(int j) { i = j; } C(const C&) { std::cout << "A copy was made.\n"; } int i;};C f(bool cond = false) { C first(101); C second(102); // the function may return one of two named objects // depending on its argument. RVO might not be applied return cond ? first : second;}int main() { std::cout << "Hello World!\n"; C obj = f(true);}
編譯技術
計算機語言之所以能由單一的機器語言發展到現今的數千種高級語言,就是因為有了編譯技術。編譯技術是計算機語言發展的支柱,也是計算機科學中發展最迅速、最成熟的一個分支,它集中體現了計算機發展的成果與精華。 編譯技術的核心思想就是把同樣的邏輯結構和思想從一種語言表示轉化為另外一種語言表示。從高級語言,甚至是運行於虛擬平台的高級語言,到機器語言,最終到硬體執行的物理信號,這一層層轉化,無一不涉及到“編譯”這個概念的套用。編譯程式是一個足夠複雜的程式,語言功能的完善,硬體結構的發展,環境的友好要求,都對編譯程式提出了更高的要求。因此一個編譯系統的構造並非易事,對完全想用手工方法來構造編譯器來說更是如此。要構造一個完全獨立的全新的編譯器可能性很小,大部分可在現有編譯器的基礎上擴展,有的採用自展的方式,有的則用自編譯的方式。編譯最佳化常用方法有:常量傳播,在編譯最佳化時,能夠將計算出結果的變數直接替換為常量。常量摺疊,在編譯最佳化時,多個變數進行計算時,而且能夠直接計算出結果,那么變數將有常量直接替換。複寫傳播,兩個相同的變數可以用一個代替。公共子表式消除, 如果一個表達式E已經計算過了,並且從先前的計算到E中的變數都沒有發生變化,那么E的此次出現就成為了公共子表達式。無用代碼消除,永遠不能被執行到的代碼或者沒有任何意義的代碼會被清除掉。
數組範圍檢查消除,數組邊界檢查不是必須在運行期間一次不漏的檢查,而是可以協商的。如果及時編譯器能根據數據流分析出變數的取值範圍在[0,max_length]之間,那么在循環期間就可以把數組的上下邊界檢查消除。方法內聯 ,編譯器最終要的最佳化手段,可減少方法調用的成本,並未其他最佳化做基礎。
逃逸分析,分析對象動態作用域,一旦確定對象不會發生方法逃逸和執行緒逃逸,就可以對這個變數進行高效的最佳化,比如棧上分配、同步消除、標量替換等。