1. 程式人生 > >12 More Effective C++—條款16/17 (2/8原理與延緩求值)

12 More Effective C++—條款16/17 (2/8原理與延緩求值)

1 “2/8”原理

二八原理指一件事情的20%需要投入80%的精力來做,即要分清主次點。這種情況在程式編寫的時候尤為突出。關鍵效能點、重要邏輯程式碼一般都是集中在小部分割槽域,而這部分割槽域需要我們特別關注。

在排查程式的新能瓶頸時,我們需要藉助工具:profiler——程式分析器。比較知名的有Google profiler,其可以檢測程式的執行時間和記憶體消耗情況。

我們要使用可重現的測試用例對程式進行測試,否則無法知道程式瓶頸、問題到底出在什麼地方。

2 延緩求值

“延緩求值”指只有在真正用到該值的時候,我們再計算出所需要的數值。常常程式碼的組合邏輯與程式的執行邏輯不相符。如下面的程式碼,雖然所有的值都在函式的開頭宣告,但有些值在程式後面才會用到。

void func() {
	int a = geta(), b = getb();
	double c = getc(), d = getd();
	// do something0
	process(a);
	// do something1
	process(b);
	// do something2
	process(c);
}

1 引用計數 (reference-count)

字串s1賦值給s2時,有如下兩種方案。方案1被稱作eager evaluate(立即求值), 方案2被稱作lazy evalute(延緩求值)。由於方案1在每次賦值的時候都會複製一遍內容,無論s2是否用得到,都會造成極大開銷。

1,直接複製s1中的內容給s2 2,s2只是指向s1, 只有對s2修改時,才複製s1中的內容。

引用計數的情況時,若多個物件內容相同,則共享一個副本。一旦某個物件內容發生變化,則複製並修改對應的副本。這樣做節省記憶體,且提高了程式的執行效率——只有在需要的時候,帶來複制資料的開銷。

引用計數不僅可以降低資料的複製次數,後面的文章將會詳細介紹,使用該技術管理動態分配記憶體。其中使用的例子是“共享智慧指標shared_ptr”。

2 區分讀寫

,進一步發現,如果只是修改一個長字串中的一個字元,是否需要複製整個字串。 如下面程式碼所示。在“引用計數”的基礎上,讀取s2中的一個字元代價很小——可能採用引用計數。但如果修改s2中的一個字元,可能會複製整個字串,開銷較大。由此提出兩個問題:

1,是否可以區分讀寫操作? 2,寫操作是否可以不復制整個字串?

string s1 = "hello hello hello hello hello", s2 = s1; // 一個長字串,“逗號操作符”保證了求值順序是從左向右
std::cout << s2[0];
s2[3] = 'd';

3 延緩訪問 (lazy fetch)

資料庫中某個表包含若干行資料,每行資料對應一個下面程式碼所示的物件。每個物件包含大量欄位,如果一次性讀入一行,建立一個物件,將造成巨大開銷。

class LargeObject {
	public:
		LargeObject(ObjectId id);
		std::list<std::string> m_fieldList;
}

現在我們採用“延遲”思想,每次需要構建表中一行資料物件時,只需要建立一個“空殼”,只有到真正需要資料時,再從資料庫中讀取某個欄位的資料。從而加速“單次響應”。

這種做法被稱作“on-demand”做法。

“on-demand”技術被應用於計算機系統中的各種場景,比如從硬碟中讀取資料、計算資料等。其實質是將一次大任務量、耗時比較長的請求,拆解成多個小請求,使只是根據當前需要進行資料訪問或計算。需要注意,所有小請求的開銷總和 >= 一次總的請求的開銷。

比如日常生活中,陡坡比較難爬,而緩坡比較容易。但是上升同樣的高度,緩坡需要走更長的距離,做更多的功,消耗更多的能量。

4 表示式延遲求值

本文最後一種“on-demand”技術的應用是,推遲一些複雜的計算。計算兩個1000 * 1000矩陣的乘法將會消耗大量資源:計算資源,儲存資源。但是我們可能只需要計算結果的一小部分資料。那麼對其他資料的計算與儲存都做了無用功。

因此,可以採用下面方式推遲計算:

1,直到真正需要訪問某個資料時,才進行復雜的計算。 2,減少賦值操作,只有在資料真正需要的地方,再去訪問最後的計算結果。

通常,計算軟體會給出這些方面的優化,比如matlab, APL等。

3 C++語言特性知識——mutable

mutable關鍵字用於修飾類欄位,使這些欄位可以在const函式中被修改。如下面所示:

class MutableClass {
	public:
		void readButModify() const; // 如果不加mutable修飾,m_member只能被讀取,不能在本函式中修改
	private:
		mutable int m_member;
}
void MutableClass::readButModify() {
	m_member = 0;
}