提高C++性能的编程技术笔记:构造函数和析构函数+测试代码
對象的創建和銷毀往往會造成性能的損失。在繼承層次中,對象的創建將引起其先輩的創建。對象的銷毀也是如此。其次,對象相關的開銷與對象本身的派生鏈的長度和復雜性相關。所創建的對象(以及其后銷毀的對象)的數量與派生的復雜度成正比。
并不是說繼承根本上就是代碼性能的絆腳石。我們必須區分全部計算開銷、必須開銷和計算損失(computional penalty). 全部計算開銷是一次計算中所執行的全部指令的總和。必須開銷是全部指令的子集,它的結果是必要的。這部分計算是必需的,其余部分即為計算損失。計算損失是可以通過別的設計和實現來消除的那部分計算。
???????? 我們不能斷言采用了復雜的繼承的設計一定是壞的,也不能斷定它們總是帶來性能損失。我們只能說總的開銷會隨著派生樹規模的增長而增加。如果所有的計算都是有價值的,那么它們都是必須的開銷。實際上,繼承層次不見得是完善的,在這種情況下,它們很可能會導致計算損失。
???????? 對象的復合與繼承一樣,都引入了與對象創建和銷毀有關的類似性能問題。在對象被創建(或銷毀)時,必須同時創建(或銷毀)它所包含的成員對象。
???????? 創建和銷毀被包含對象是另一個值得注意的問題:在創建(或銷毀)被包含對象時無法阻止子對象的創建(或銷毀),因為這是編譯器自動強加的步驟。
???????? 性能優化經常需要犧牲一些其它軟件目標,諸如靈活性、可維護性、成本和重用之類的重要目標經常必須為性能讓步。
???????? 在C++中,不自覺地在程序開始處預先定義所有對象的做法是一種浪費。因為這樣可能會創建一些直到最后都沒有用到的對象。在C++中,把變量的創建延遲到第一次使用前。
???????? 構造函數和析構函數可以像手工編寫的C代碼一樣有效。然而在實踐中,它們經常包含冗余計算。
???????? 對象的創建(或銷毀)觸發對父對象和成員對象的遞歸創建(或銷毀)。
???????? 要確保所編寫的代碼實際使用了所有創建的對象和這些對象所執行的計算。
???????? 對象的生命周期不是無償的。至少對象的創建和銷毀會消耗CPU周期。不要隨意創建一個對象,除非你打算使用它。通常情況下,要等到需要使用對象的地方再創建它。
???????? 編譯器必須初始化被包含的成員對象之后再執行構造函數體。你必須在初始化階段完成成員對象的創建。這可以降低隨后在構造函數部分調用賦值操作符的開銷。在某些情況下,這樣也可以避免臨時對象的產生。
以下是測試代碼(constructors_and_destructors.cpp):
#include "constructors_and_destructors.hpp"
#include <iostream>
#include <string>
#include <mutex>
#include <chrono>namespace constructors_destructors_ {// reference: 《提高C++性能的編程技術》:第二章:構造函數和析構函數class SimpleMutex { // 單獨的鎖類
public:SimpleMutex(std::mutex& mtx) : mymtx(mtx) { acquire(); }~SimpleMutex() { release(); }private:void acquire() { mymtx.lock(); }void release() { mymtx.unlock(); }std::mutex& mymtx;
};class BaseMutex { // 基類
public:BaseMutex(std::mutex& mtx) {}virtual ~BaseMutex() {}
};class DerivedMutex : public BaseMutex {
public:DerivedMutex(std::mutex& mtx) : BaseMutex(mtx), mymtx(mtx) { acquire(); }~DerivedMutex() { release(); }private:void acquire() { mymtx.lock(); }void release() { mymtx.unlock(); }std::mutex& mymtx;};class Person1 {
public:Person1(const char* s) { name = s; } // 隱式初始化和顯示賦值private:std::string name;
};class Person2 {
public:Person2(const char* s) : name(s) {} // 顯示初始化private:std::string name;
};int test_constructors_destructors_1()
{// 測試三種互斥鎖的實現// Note:與書中實驗結果有差異,在這里繼承對象并不會占用較多的執行時間,在這里這三種所占用時間基本差不多using namespace std::chrono;high_resolution_clock::time_point time_start, time_end;const int cycle_number {100000000};int shared_counter {0};{ // 1.直接調用mutexstd::mutex mtx;shared_counter = 0;time_start = high_resolution_clock::now();for (int i = 0; i < cycle_number; ++i) {mtx.lock();++shared_counter;mtx.unlock();}time_end = high_resolution_clock::now();std::cout<< "time spen1: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<< " seconds\n";
}{ // 2.不從基類繼承的獨立互斥對象std::mutex mtx;shared_counter = 0;time_start = high_resolution_clock::now();for (int i = 0; i < cycle_number; ++i) {SimpleMutex m(mtx);++shared_counter;}time_end = high_resolution_clock::now();std::cout<< "time spen2: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";
}{ // 3.從基類派生的互斥對象std::mutex mtx;shared_counter = 0;time_start = high_resolution_clock::now();for (int i = 0; i < cycle_number; ++i) {DerivedMutex m(mtx);++shared_counter;}time_end = high_resolution_clock::now();std::cout<< "time spen3: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";}// 隱式初始化和顯示賦值與顯示初始化性能對比:使用顯示初始化操作要優于使用隱式初始化和顯示賦值操作
{ // 1.隱式初始化和顯示賦值操作time_start = high_resolution_clock::now();for (int i = 0; i < cycle_number; ++i) {Person1 p("Pele"); }time_end = high_resolution_clock::now();std::cout<< "隱式初始化, time spen: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";
}{ // 2.顯示初始化操作time_start = high_resolution_clock::now();for (int i = 0; i < cycle_number; ++i) {Person2 p("Pele");}time_end = high_resolution_clock::now();std::cout<<"顯示初始化, time spen: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";
}return 0;
}} // namespace constructors_destructors_
運行結果如下:
GitHub:?https://github.com/fengbingchun/Messy_Test
總結
以上是生活随笔為你收集整理的提高C++性能的编程技术笔记:构造函数和析构函数+测试代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 提高C++性能的编程技术笔记:跟踪实例+
- 下一篇: Windows10上使用VS2017编译