1. 程式人生 > >提高C++效能的程式設計技術筆記:虛擬函式、返回值優化+測試程式碼

提高C++效能的程式設計技術筆記:虛擬函式、返回值優化+測試程式碼

虛擬函式:在以下幾個方面,虛擬函式可能會造成效能損失:建構函式必須初始化vptr(虛擬函式表);虛擬函式是通過指標間接呼叫的,所以必須先得到指向虛擬函式表的指標,然後再獲得正確的函式偏移量;內聯是在編譯時決定的,編譯器不可能把執行時才解析的虛擬函式設定為內聯

無法內聯虛擬函式造成的效能損失最大。

某些情況下,在編譯期間解析虛擬函式的呼叫是可能的,但這是例外情況。由於在編譯期間不能確定所呼叫的函式所屬的物件型別,所以大多數虛擬函式呼叫都是在執行期間解析的。編譯期間無法解析對內聯造成了負面影響。由於內聯是在編譯期間確定的,所以它需要具體函式的資訊,但如果在編譯期間不能確定將呼叫哪個函式,就無法使用內聯。

評估虛擬函式的效能損失就是評估無法內聯該函式所造成的損失。這種損失的代價並不固定,它取決於函式的複雜程度和呼叫頻率。一種極端情況是頻繁呼叫的簡單函式,它們是內聯的最大受益者,若無法內聯則會造成重大效能損失。另一極端情況是很少呼叫的複雜函式。

通過對類選擇進行硬編碼或者將它作為模板引數來傳遞,可以避免使用動態繫結。

因為函式呼叫的動態繫結是繼承的結果,所以消除動態繫結的一種方法是用基於模板的設計來替代繼承。模板把解析的步驟從執行期間提前到編譯期間,從這個意義上說,模板提高了效能。而對於我們所關心的編譯時間,適當增加也是可以接受的。

返回值優化通過轉換原始碼和消除物件的建立來加快原始碼的執行速度,這種優化稱為返回值優化(Return Value Optimization, RVO)

編譯器優化要保證原來計算的正確性。然而對於RVO來說,這一點並不總是易於實現的。既然RVO不是強制執行的,編譯器就不會對複雜的函式執行RVO。例如,如果函式有多個return語句返回不同名稱的物件,這樣就不會執行RVO。如果想使用RVO,就必須返回相同名稱的物件。

當編譯器無法執行RVO時,可按計算性建構函式的形式來實現。

如果必須按值返回物件,通過RVO可以省去建立和銷燬區域性物件的步驟,從而改善效能。

RVO的應用要遵照編譯器的實現而定。這需要參考編譯器文件或通過實驗來判斷是否使用RVO以及何時使用。

通過編寫計算性建構函式可以更好地使用RVO。

以下是測試程式碼(return_value_optimization.cpp):

#include "return_value_optimization.hpp"
#include <iostream>
#include <chrono>

namespace return_value_optimization_ {

// reference: 《提高C++效能的程式設計技術》:第四章:返回值優化

class Complex {
	friend Complex operator + (const Complex&, const Complex&);
	friend void Complex_Add(const Complex&, const Complex&, Complex&);
	friend Complex Complex_Add2(const Complex&, const Complex&);
	friend Complex Complex_Add3(const Complex&, const Complex&);

public:
	Complex(double r =0.0, double i =0.0) : real(r), imag(i) {} // 預設建構函式
	Complex(const Complex& c) : real(c.real), imag(c.imag) {} // 拷貝建構函式
	Complex(const Complex& a, const Complex& b) : real(a.real + b.real), imag(a.imag + b.imag) {} // 計算性建構函式
	Complex& operator = (const Complex& c) { this->real = c.real; this->imag = c.imag; return *this; }// 賦值運算子
	~Complex() {}

private:
	double real;
	double imag;

};

Complex operator + (const Complex& a, const Complex& b)
{
	Complex retVal;
	retVal.real = a.real + b.real;
	retVal.imag = a.imag + b.imag;

	return retVal;
}

// 消除區域性物件retVal,直接把返回值放到__tempResult臨時物件中來實現優化,這就是返回值優化(RVO)
void Complex_Add(const Complex& a, const Complex&b, Complex& __tempResult)
{
	__tempResult.real = a.real + b.real;
	__tempResult.imag = a.imag + b.imag;
}

Complex Complex_Add2(const Complex& a, const Complex& b)
{
	Complex retVal;
	retVal.real = a.real + b.real;
	retVal.imag = a.imag + b.imag;

	return retVal;
}

// 通過計算性建構函式來執行加操作
Complex Complex_Add3(const Complex& a, const Complex& b)
{
	return Complex(a, b);
}

int test_return_value_optimization_1()
{
	// 測試兩種加操作的實現效能:普通加操作、RVO加操作
	using namespace std::chrono;
	high_resolution_clock::time_point time_start, time_end;
	const int cycle_number {100000000};

{ // 普通加操作
	Complex a(1, 0);
	Complex b(2, 0);
	Complex c;

	time_start = high_resolution_clock::now();
	for (int i = 0; i < cycle_number; ++i) {
		c = a + b;
	}
	time_end = high_resolution_clock::now();
	std::cout<<"common add time spent: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";
}

{ // RVO加操作
	Complex a(1, 0);
	Complex b(2, 0);
	Complex c;
	
	time_start = high_resolution_clock::now();
	for (int i = 0; i < cycle_number; ++i) {
		Complex_Add(a, b, c);
	}
	time_end = high_resolution_clock::now();
	std::cout<<"RVO add time spent: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";
}

	// 測試兩種加操作的實現效能:普通加操作、使用計算性建構函式

{ // 普通加操作
	Complex a(1, 0);
	Complex b(2, 0);
	Complex c;

	time_start = high_resolution_clock::now();
	for (int i = 0; i < cycle_number; ++i) {
		c = Complex_Add2(a, b);
	}
	time_end = high_resolution_clock::now();
	std::cout<<"common add time spent: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";
}

{ // 使用計算性建構函式
	Complex a(1, 0);
	Complex b(2, 0);
	Complex c;

	time_start = high_resolution_clock::now();
	for (int i = 0; i < cycle_number; ++i) {
		c = Complex_Add3(a, b);
	}
	time_end = high_resolution_clock::now();
	std::cout<<"計算性建構函式 add time spent: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";

}

	return 0;
}

} // namespace return_value_optimization_

執行結果如下:

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