日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

C++ 核心指南 —— 性能

發(fā)布時間:2023/12/29 windows 38 coder
生活随笔 收集整理的這篇文章主要介紹了 C++ 核心指南 —— 性能 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

C++ 核心指南 —— 性能

閱讀建議:先閱讀 《性能優(yōu)化的一般策略及方法》

截至目前,C++ Core Guidelines 中關(guān)于性能優(yōu)化的建議共有 18 條,而其中很大一部分是告誡你,不要輕易優(yōu)化!

非必要,不優(yōu)化

  • Per.1: 不要無故優(yōu)化
  • Per.2: 不要過早優(yōu)化
  • Per.3: 只優(yōu)化少數(shù)關(guān)鍵代碼

前三條可以總結(jié)為:非必要,不優(yōu)化。所謂的“優(yōu)化”,是指犧牲可讀性、可維護性,以換取性能提升(否則應(yīng)該作為編程的標準實踐)。優(yōu)化可能引入新的 bug,增加維護成本。軟件工程師應(yīng)把重心放在編寫簡潔、易于理解和維護的代碼,而不是把性能作為首要目標。

先測量,再優(yōu)化

如果性能非常重要,應(yīng)該通過精確地測量,找到程序的 hot spots,再有針對性地優(yōu)化。

Per.4: 不要假設(shè)復(fù)雜的代碼比簡單的代碼快

  • 多線程未必比單線程快:考慮到線程間同步的開銷、上下文切換開銷,多線程未必比單線程快
  • 利用一系列復(fù)雜的優(yōu)化技巧編寫的復(fù)雜代碼未必比直接編寫的簡單代碼快,如
// 好:簡單直接
vector<uint8_t> v(100000);

for (auto& c : v)
    c = ~c;
// 不好:復(fù)雜的優(yōu)化技巧,本意想更快,但往往更慢!
vector<uint8_t> v(100000);

for (size_t i = 0; i < v.size(); i += sizeof(uint64_t)) {
    uint64_t& quad_word = *reinterpret_cast<uint64_t*>(&v[i]);
    quad_word = ~quad_word;
}

Per.5: 不要假設(shè)低級語言比高級語言快

不要低估編譯器的優(yōu)化能力,很多時候編譯器產(chǎn)生的代碼要比手動編寫低級語言更高效!

Per.6: 沒有測量就不要對性能妄下斷言

  • 性能優(yōu)化很多時候是反直覺的,針對某些條件下的性能優(yōu)化技巧在另一個環(huán)境下可能會劣化性能,因此必須要測量才知道某個改動到底會“優(yōu)化”還是“劣化”性能
  • 小于 4% 的代碼能占用 50% 的程序執(zhí)行時間。只有測量才知道時間花在哪里,才能有針對性地優(yōu)化

以上 6 條建議在 《性能優(yōu)化的一般策略及方法》 中有更詳細的描述。

具體優(yōu)化建議

Per.7 設(shè)計應(yīng)當允許優(yōu)化

總是需要優(yōu)化最初的設(shè)計,如果設(shè)計之初完全忽視了將來優(yōu)化的可能性,會導(dǎo)致很難修改。

過早優(yōu)化是萬惡之源,但這并不是輕視性能的借口。一些經(jīng)過實踐檢驗的最佳實踐可以幫助我們寫出高效、可維護、可優(yōu)化的代碼:

  • 信息傳遞:接口設(shè)計要干凈,但還要攜帶足夠的信息,以便后續(xù)改進實現(xiàn)。
  • 緊湊的數(shù)據(jù)結(jié)構(gòu):默認情況下,使用緊湊的數(shù)據(jù)結(jié)構(gòu),如 std::vector,如果你認為需要一個鏈表,嘗試設(shè)計接口,使用戶看不到這個結(jié)構(gòu)(參考標準庫算法的接口設(shè)計)。
  • 函數(shù)參數(shù)的傳遞和返回:區(qū)分可變和不可變數(shù)據(jù)。不要把 資源管理 的任務(wù)強加給用戶。不要把假想的 indirection 強加給用戶。使用常規(guī)的方式傳遞信息,非常規(guī)或為特定實現(xiàn)“優(yōu)化”過的數(shù)據(jù)傳遞方式可能會導(dǎo)致后續(xù)難以修改實現(xiàn)。
  • 抽象:不要過度泛化。試圖滿足每種可能的使用情況(包括誤用),把每個設(shè)計決策推遲(編譯或運行時 indirection)會導(dǎo)致復(fù)雜、臃腫、難以理解。不要基于對未來需求的猜測來進行泛化,從具體示例中進行泛化。泛化時保持性能,理想狀態(tài)是零開銷泛化。
  • 庫:選擇具有良好接口設(shè)計的庫。如果沒有現(xiàn)成的,自己寫一個,模仿具有良好接口風格的庫(可以從標準庫找靈感)。
  • 隔離:把你的代碼和舊的、亂的代碼隔離開。可以按照自己的風格,設(shè)計一個接口風格良好的 wrapper,把那些不得不用的舊的、亂的代碼封裝起來,不要污染到我們自己的代碼。

"indirection"(間接)通常指的是通過引入額外的層級或中介來訪問數(shù)據(jù)或功能。在 C++ 中,這可能涉及使用指針、引用或其他間接方式來訪問變量、對象或函數(shù)。

  1. 設(shè)計接口時,不要只考慮第一版的用例和實現(xiàn)。初版實現(xiàn)之后,必須 review,因為一旦部署之后,彌補錯誤將很困難。
  2. 低級語言并不總是高效。高級語言的代碼不一定慢。
  3. 任何操作都有開銷,不用過分擔心開銷(現(xiàn)代計算機都足夠的快),但是需要大致了解各種操作的開銷。例如:內(nèi)存訪問、函數(shù)調(diào)用、字符串比較、系統(tǒng)調(diào)用、磁盤訪問、網(wǎng)絡(luò)通信。
  4. 不是每段代碼都需要穩(wěn)定接口,有的接口可能只是實現(xiàn)細節(jié)。但還是要停下來想一下:如果要使用多個線程實現(xiàn)這個操作,需要什么樣的接口?是否可以向量化?”
  5. 本條目和 Per.2 并不矛盾,而是它的補充:鼓勵開發(fā)者在必要且時機成熟時進行優(yōu)化。

移動語義

《C++ Core Guidelines 解析》針對本條目重點補充了移動語義:寫算法時,應(yīng)使用移動語義,而不是拷貝。移動語義有以下好處:

  • 移動開銷比拷貝低
  • 算法穩(wěn)定,因為不需要分配內(nèi)存,不會出現(xiàn) std::bad_alloc 異常
  • 算法可以用于“只移類型”,如 std::unique_ptr

需要移動語義的算法遇到不支持移動操作類型,則自動“回退”到拷貝操作。
而只支持拷貝語義的算法遇到不支持拷貝操作的類型時,則編譯報錯。

Per.10 依賴靜態(tài)類型系統(tǒng)

弱類型(如 void* )、低級代碼(如把 sequence 作為單獨的字節(jié)來操作)會讓編譯器難以優(yōu)化。

《解析》中還給出了一些額外的幫助編譯器生成優(yōu)化代碼的技巧:

  1. 本地代碼。“本地”指在同一個編譯單元(如同一個 .c/.cpp 文件中)。例如 std::sort 需要一個謂詞,傳入本地 lambda 可能會比傳入函數(shù)(指針)更快。
    因為對于本地 lambda,編譯器擁有所有可用的信息來生成最優(yōu)代碼,而函數(shù)可能定義在另一個編譯單元中,編譯器無法獲取有關(guān)該函數(shù)的細節(jié),從而無法進行深度優(yōu)化。
  2. 簡單代碼。優(yōu)化器會搜尋可以被優(yōu)化的已知模式,簡單的代碼更容易被匹配到。如果是手寫的復(fù)雜代碼,反而可能錯失讓編譯器優(yōu)化的機會。
  3. 額外提示。constnoexceptfinal 等關(guān)鍵字可以給編譯器提供額外的信息,有了這些額外的信息,編譯器可以大膽地做進一步優(yōu)化。當然要先搞清楚這些關(guān)鍵字的含義及產(chǎn)生的影響。

Per.11 將計算從運行時提前到編譯期

可以減少代碼尺寸和運行時間、避免數(shù)據(jù)競爭、減少運行期的錯誤處理。

constexpr

將函數(shù)聲明為 constexpr,且參數(shù)都是常量表達式,則可以在編譯期執(zhí)行。

注意:constexpr 函數(shù)可以在編譯期執(zhí)行,但不意味著只能在編譯期執(zhí)行,也可以在運行期執(zhí)行。

constexpr 函數(shù)的限制:

  • 不能使用 staticthread_local 變量
  • 不能使用 goto
  • 不能使用異常
  • 所有變量必須初始化為字面類型

字面類型:

  • 內(nèi)置類型(及其引用)
  • constexpr 構(gòu)造的類
  • 字面類型的數(shù)組

例 1

// 舊風格:動態(tài)初始化
double square(double d) { return d*d; }
static double s2 = square(2);

// 現(xiàn)代風格:編譯期初始化
constexpr double ntimes(double d, int n)   // 假設(shè) 0 <= n
{
    double m = 1;
    while (n--) m *= d;
    return m;
}
constexpr double s3 {ntimes(2, 3)};

第一種寫法很常見,但有兩個問題:

  • 運行時函數(shù)調(diào)用開銷
  • 另一個線程可能在 s2 初始化之前訪問 s2

注:常量不存在數(shù)據(jù)競爭的問題

例 2

一個常用的技巧,小對象直接存在 handle 里,大對象存在堆上。

constexpr int on_stack_max = 20;

// 直接存儲
template<typename T>
struct Scoped {
    T obj;
};

// 在堆上存儲
template<typename T>
struct On_heap {
    T* objp;
};

template<typename T>
using Handle = typename std::conditional<
    (sizeof(T) <= on_stack_max),
    Scoped<T>,
    On_heap<T>
>::type;

void f()
{
    // double 在棧上
    Handle<double> v1;
    // 數(shù)組在堆上
    Handle<std::array<double, 200>> v2;
}

編譯期可以計算出最佳類型,類似地技術(shù)也可用于在編譯期選擇最佳函數(shù)。

實際上大多數(shù)計算取決于輸入,不可能把所有的計算全部放到編譯期。除此之外,復(fù)雜的編譯期計算可能大幅增加編譯時間,并且導(dǎo)致調(diào)試困難。甚至在極少場景下,可能導(dǎo)致性能劣化。

代碼檢查建議

  • 檢查是否有簡單的、可以作為(但沒有) constexpr 的函數(shù)
  • 檢查是否有函數(shù)的所有參數(shù)都是常量表達式
  • 檢查是否有可以改為 constexpr 的宏

Per.19 以可預(yù)測的方式訪問內(nèi)存

緩存對性能影響很大,一般緩存算法對相鄰數(shù)據(jù)的簡單、線性訪問效率更高。

當程序需要從內(nèi)存中讀取一個 int 時,現(xiàn)代計算機架構(gòu)會一次讀取整個緩存行(通常 64 字節(jié)),儲存在 CPU 緩存中,如果接下來要讀取的數(shù)據(jù)已經(jīng)在緩存中,則會直接使用,快很多。

例如:

int matrix[rows][cols];

// 不好
for (int c = 0; c < cols; ++c)
    for (int r = 0; r < rows; ++r)
        sum += matrix[r][c];

// 好
for (int r = 0; r < rows; ++r)
    for (int c = 0; c < cols; ++c)
        sum += matrix[r][c];

在 C++ 標準庫中,std::vector, std::array, std::string 將數(shù)據(jù)存在連續(xù)的內(nèi)存塊中的數(shù)據(jù)結(jié)構(gòu)對緩存行很友好。而 std::liststd::forward_list 則恰恰相反。
例如在某測試環(huán)境中,從容器中讀取并累加所有元素:

  • std::vectorstd::liststd::forward_list 快 30 倍
  • std::vectorstd::deque 快 5 倍

很多場景下,即使需要在中間插入/刪除元素,由于緩存行的原因,std::vector 的性能也可能好于 std::list

除非測量的結(jié)果表明其他容器性能好于 std::vector,否則應(yīng)將 std::vector 作為首選容器。

其他

剩下的條目截至目前還只有標題,缺少詳細描述:

  • Per.12 Eliminate redundant aliases/消除冗余別名
  • Per.13 Eliminate redundant indirections/消除冗余間接引用(指針解引用)
  • Per.14 Minimize the number of allocations and deallocations/盡可能減少分配和釋放
  • Per.15 Do not allocate on a critical branch/不在關(guān)鍵分支上分配
  • Per.16 Use compact data structures/使用緊湊的數(shù)據(jù)結(jié)構(gòu):性能主要由內(nèi)存訪問決定
  • Per.17 Declare the most used member of a time-critical struct first/對于時間關(guān)鍵的結(jié)構(gòu)體,把最常用的成員定義在前
  • Per.18 Space is time/空間就是時間:性能主要由內(nèi)存訪問決定
  • Per.30 Avoid context switches on the critical path/避免關(guān)鍵路徑上的上下文切換

總結(jié)

  • 非必要,不優(yōu)化
  • 先測量,再優(yōu)化
  • 為編譯器優(yōu)化提供必要信息:
    • 正確使用 const、final、noexcept 等關(guān)鍵字
    • 為函數(shù)實現(xiàn)移動語義、如果可能,使之成為 constexpr
  • 現(xiàn)代計算機架構(gòu)為連續(xù)讀取內(nèi)存而進行了優(yōu)化,應(yīng)該將 std::vector, std::array, std::string 作為首選

Reference

  • C++ Core Guidelines, Per: Performance
  • 《性能優(yōu)化的一般策略及方法》
  • 《C++ Core Guidelines 解析》

總結(jié)

以上是生活随笔為你收集整理的C++ 核心指南 —— 性能的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。