提高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_
執行結果如下: