簡體   English   中英

在C ++中“如果錯誤然后快速失敗”的性能損失?

[英]Performance penalty for “if error then fail fast” in C++?

兩種寫入if-else的樣式之間是否存在任何性能差異(在C ++中),如下所示(邏輯等效代碼)對於likely1 == likely2 == true路徑( likely1likely2在這里作為占位符用於更精細的一些條件)?

// Case (1):
if (likely1) {
  Foo();
  if (likely2) {
    Bar();
  } else {
    Alarm(2);
  }
} else {
  Alarm(1);
}

// Case (2):
if (!likely1) {
  Alarm(1);
  return;
}
Foo();
if (!likely2) {
  Alarm(2);
  return;
}
Bar();

我非常感謝有關盡可能多的編譯器和平台的信息(但突出顯示了gcc / x86)。

請注意我對這兩種風格的可讀性意見感興趣,也沒有任何“過早優化”聲明。

編輯:換句話說,我想詢問這兩種樣式是否在某些時候被編譯器完全認為是完全100%等效/透明的(例如,在特定編譯器中某些點上的逐位等效AST) ,如果沒有,那么有什么區別? 對於任何(對“現代”和gcc)編譯器的偏好,你知道。

而且,為了更清楚,我也真的不覺得它會給予我很大的性能提升,而且它通常會過早的優化,但感興趣的是是否多大程度上 提高/降低任何東西?

它在很大程度上取決於編譯器和優化設置。 如果差異至關重要 - 同時執行兩者,並分析程序集或執行基准測試。

我對特定平台沒有答案,但我可以提出一些一般觀點:

  • 在沒有分支預測的非現代處理器上的傳統答案是,第一種可能更有效,因為在通常情況下它需要更少的分支。 但你似乎對現代編譯器和處理器感興趣。

  • 在現代處理器上,一般來說,短前向分支並不昂貴,而錯誤預測的分支可能很昂貴。 當然,“昂貴”意味着幾個周期

  • 除此之外,編譯器有權訂購基本塊,但它喜歡它,但前提是它不會改變邏輯。 所以當你寫if (blah) {foo();} else {bar();} ,編譯器有權發出如下代碼:

  evaluate condition blah
  jump_if_true else_label
  bar()
  jump endif_label
else_label:
  foo()
endif_label:

總的來說,gcc傾向於按照你寫的順序大致發出東西,其他條件相同。 有各種各樣的東西使其他所有不相等,例如,如果你有邏輯等價的bar(); return 在你的函數中的兩個不同的地方bar(); return ,gcc可能很好地合並這些塊,只發出一次調用bar()然后返回,並從兩個不同的地方跳轉或掉到那個。

  • 分支預測有兩種 - 靜態和動態。 靜態意味着分支的CPU指令指定條件是否“可能”,以便CPU可以針對常見情況進行優化。 編譯器可能會在某些平台上發出靜態分支預測,如果您正在針對該平台進行優化,則可以編寫代碼來考慮這一點。 您可以通過了解編譯器如何處理各種控制結構或使用編譯器擴展來考慮它。 就個人而言,我認為它不足以概括編譯器將會做什么。 看看反匯編。

  • 動態分支預測意味着在熱代碼中,CPU將自己統計分支的可能性,並針對常見情況進行優化。 現代處理器使用各種不同的動態分支預測技術: http//en.wikipedia.org/wiki/Branch_predictor 性能關鍵代碼幾乎熱代碼,只要動態分支預測策略有效,它就可以非常快速地優化熱代碼。 可能有某些病理情況混淆了特定的策略,但總的來說,你可以說任何處於緊張循環中的任何偏向於采取/不采取的情況,將在大多數情況下被正確預測

  • 有時甚至無論分支是否正確預測都是無關緊要的,因為某些情況下某些CPU在等待條件評估時會在指令管道中包含這兩種可能性,並拋棄不必要的選項。 現代CPU變得復雜 然而,更簡單的CPU設計可以避免分支成本,例如ARM上的條件指令。

  • 無論如何,對其他功能的調用將會擾亂所有這些猜測。 因此在您的示例中可能存在細微差別,這些差異可能取決於Foo,Bar和Alarm中的實際代碼。 不幸的是,不可能區分顯着和微不足道的差異,或者考慮到這些功能的細節,而不會進入你不感興趣的“過早優化”指控。

  • 微觀優化尚未編寫的代碼幾乎總是為時過早。 很難預測名為Foo和Bar的函數的性能。 據推測,問題的目的是辨別是否存在應該為編碼風格提供信息的常見問題。 答案是,由於動態分支預測,沒有。 在熱門代碼中,條件的排列方式幾乎沒有什么區別,並且它確實產生差異,差異不像“在if條件下采取/不采用分支更快”這樣容易預測。

  • 如果這個問題只適用於單個程序,並且該代碼被證明是熱門的,那么它當然可以進行測試,沒有必要進行概括。

它取決於編譯器。 查看__builtin_expect上的gcc文檔。 您的編譯器可能有類似的東西。 請注意,您確實應該關注過早優化。

答案很大程度上取決於“可能”的類型。 如果它是一個整型常量表達式,編譯器可以對其進行優化,兩種情況都是等價的。 否則,它將在運行時進行評估,並且無法進行太多優化。

因此,情況2通常比情況1更有效。

作為我使用的實時嵌入式系統的輸入,您的“案例2”通常是安全和/或性能至關重要的代碼的標准。 安全關鍵型嵌入式系統的樣式指南通常允許使用此語法,因此函數可以在出錯時快速退出。

通常,樣式指南會對“case 2”語法不屑一顧,但是如果允許在一個函數中允許多次返回則允許異常

1)該功能需要快速退出並處理錯誤,或

2)如果函數末尾的單個返回導致代碼不太可讀,這通常是各種協議和數據解析器的情況。

如果您關注性能,我假設您正在使用配置文件引導優化。

如果您使用的是配置文件引導優化,則您提出的兩種變體完全相同。

無論如何,你所詢問的內容的性能完全被代碼示例中不明顯的性能特征所掩蓋,所以我們真的無法回答這個問題。 你必須測試兩者的性能。

雖然我和其他所有人在一起,因為優化分支是沒有意義的,如果沒有分析並且實際上已經找到了瓶頸......如果有的話,優化可能的情況是有意義的。

正如他們的名字所示,可能1和可能2都可能。 因此排除兩者都可能的組合也可能是最快的:

if(likely1 && likely2)
{
    ... // happens most of the time
}else
{
    if(likely1)
        ...
    if(likely2)
        ...
    else if(!likely1 && !likely2) // happens almost never
        ...
}

請注意,第二個else可能不是必需的,一個像樣的編譯器會發現,如果前一個if子句不是真的,那么即使你沒有明確告訴它,也是如此。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM