跳轉到內容

最佳化 C++/編寫高效程式碼/效能降低功能

來自 Wikibooks,開放書籍,為開放世界

與 C 相比,C++ 有一些功能,如果使用不當會降低效率。

其中一些功能仍然非常高效,可以在需要時隨意使用。但是,當不需要該功能時,應該避免其增加的成本。

其他功能則效率低下,因此應謹慎使用。

在本節中,將介紹一些避免降低效能的 C++ 功能的指導原則。

throw 運算子

[編輯 | 編輯原始碼]

僅當您想通知使用者當前命令失敗時才呼叫throw 運算子。

與函式呼叫相比,丟擲異常的成本非常高。它需要數千個處理器週期。如果僅在使用者介面上顯示訊息或寫入日誌檔案時丟擲異常,則可以保證它不會在沒有通知的情況下執行得太頻繁。

相反,如果異常成為演算法的一部分,即使最初認為很少見,它們最終也可能執行得太頻繁。

virtual 成員函式

[編輯 | 編輯原始碼]

僅當類至少包含另一個virtual 成員函式或類可能從(即該類旨在用作基類)派生時,才將解構函式定義為virtual。除解構函式外,除非您打算覆蓋它們,否則不要將函式宣告為virtual

具有virtual 成員函式的類所佔用的儲存空間比沒有virtual 成員函式的類多。至少包含一個virtual 成員函式的類的例項佔用更多空間(通常是 指標,可能還有一些填充),並且它們的構造需要更多時間(通常是 設定指標)比沒有virtual 成員函式的類的例項。

此外,每個virtual 成員函式的呼叫時間都比相同的非virtual 成員函式慢。

virtual 繼承

[編輯 | 編輯原始碼]

僅當多個類必須共享一個共同基類的表示形式時才使用virtual 繼承。

例如,考慮以下類定義

class A { ... };
class B1: public A { ... };
class B2: public A { ... };
class C: public B1, public B2 { ... };

對於此類定義,每個 C 類物件都包含兩個不同的類 A 物件,一個從 B1 基類繼承而來,另一個從 B2 類繼承而來。

如果類 A 沒有非static 成員變數,則沒有問題。

如果相反,類 A 包含一些成員變數,並且您希望這些成員變數對每個 C 類例項都是唯一的,則必須使用virtual 繼承,方法如下

class A { ... };
class B1: virtual public A { ... };
class B2: virtual public A { ... };
class C: public B1, public B2 { ... };

這種情況是唯一需要virtual 派生的情況。

如果使用virtual 繼承,則類 A 的成員函式在 C 類物件上呼叫的速度會略微變慢。

多型類的模板

[編輯 | 編輯原始碼]

不要定義多型類的模板。

換句話說,不要在同一個類定義中使用“template”和“virtual”關鍵字。

每次例項化類模板時,都會複製所有使用的成員函式。如果這些類包含虛擬函式,即使它們的vtableRTTI 資料也會被複制。這些資料會使程式碼膨脹。

自動釋放器的使用

[編輯 | 編輯原始碼]

僅當您可以證明其對特定情況的便利性時,才使用基於垃圾收集 或某種引用計數智慧指標(例如Boost 庫中的shared_ptr)。

垃圾收集或未引用記憶體的自動回收,允許程式設計師省略記憶體釋放呼叫的寫法,並防止記憶體洩漏。但是,您必須透過非標準庫實現垃圾收集,因為這些功能不包含在 C++ 中。此外,使用垃圾收集的程式碼通常比使用顯式釋放(當顯式呼叫delete 運算子時)的程式碼執行速度慢。此外,當垃圾收集器執行時,程式的其餘部分不會執行。這使得程式的執行不確定性。

C++98 標準庫只包含一種智慧指標auto_ptr,這種智慧指標非常高效。非標準庫提供的其他智慧指標(例如 Boost)將由 C++11 標準庫提供。智慧指標依賴於引用計數,但效率低於簡單指標。例如,shared_ptr 是標準的 Boost 智慧指標。但是,如果使用 Boost 的執行緒安全版本,則該庫在建立、銷燬和複製這些指標時的效能非常差,因為它必須保證這些操作的互斥訪問。

通常,您應該嘗試在設計時將每個動態分配的物件分配給單個所有者。當這種分配很困難時,例如如果多個物件傾向於將銷燬物件的責任傳遞給彼此,那麼使用引用計數智慧指標來處理該物件會很方便。

volatile 修飾符

[編輯 | 編輯原始碼]

僅將由硬體裝置非同步更改的變數定義為volatile

使用volatile 修飾符可以阻止編譯器將變數定位到暫存器中,即使是短暫的時間。這保證了所有裝置都看到相同的變數,但會減慢訪問該變數的操作速度。

volatile 不能保證其他執行緒會看到相同的值,因為它不會強制編譯器生成必要的記憶體屏障和鎖指令。因此,即使在同一個 CPU 上(在中斷和訊號的情況下很重要),尤其是跨記憶體快取和匯流排訪問其他 CPU,對 volatile 值的讀寫訪問也可能無序進行。您必須使用適當的執行緒或原子記憶體 API 或編寫機器程式碼來保證操作的正確順序,以確保執行緒安全。

華夏公益教科書