簡體   English   中英

在模數之前和分配操作之前,`if`語句是否冗余?

[英]Is the `if` statement redundant before modulo and before assign operations?

考慮下一個代碼:

unsigned idx;
//.. some work with idx
if( idx >= idx_max )
    idx %= idx_max;

可以簡化為僅第二行:

idx %= idx_max;

並將取得同樣的結果。


好幾次我遇到了下一個代碼:

unsigned x;
//... some work with x
if( x!=0 )
  x=0;

可以簡化為

x=0;

問題:

  • 有沒有意義使用if和為什么? 特別是使用ARM Thumb指令集。
  • 難道這些if s內被遺漏?
  • 編譯器有什么優化?

如果你想了解編譯器正在做什么,你需要提取一些程序集。 我推薦這個網站(我已經從問題中輸入了代碼)): https//godbolt.org/g/FwZZOb

第一個例子更有趣。

int div(unsigned int num, unsigned int num2) {
    if( num >= num2 ) return num % num2;
    return num;
}

int div2(unsigned int num, unsigned int num2) {
    return num % num2;
}

產生:

div(unsigned int, unsigned int):          # @div(unsigned int, unsigned int)
        mov     eax, edi
        cmp     eax, esi
        jb      .LBB0_2
        xor     edx, edx
        div     esi
        mov     eax, edx
.LBB0_2:
        ret

div2(unsigned int, unsigned int):         # @div2(unsigned int, unsigned int)
        xor     edx, edx
        mov     eax, edi
        div     esi
        mov     eax, edx
        ret

基本上,出於非常具體和邏輯的原因,編譯器不會優化分支。 如果整數除法與比較的成本大致相同,那么分支將是毫無意義的。 但整數除法(模數與典型值一起執行)實際上非常昂貴: http//www.agner.org/optimize/instruction_tables.pdf 這些數字因架構和整數大小而異,但通常可能是從15到接近100個周期的延遲。

通過在執行模數之前選擇分支,您實際上可以節省大量的工作。 請注意:編譯器也不會將沒有分支的代碼轉換為程序集級別的分支。 那是因為分支也有一個缺點:如果最終需要模數,那你就浪費了一點時間。

在不知道idx < idx_max將為真的相對頻率的情況下,無法對正確的優化做出合理的確定。 所以編譯器(gcc和clang做同樣的事情)選擇以相對透明的方式映射代碼,將這個選擇留給開發人員。

所以這個分支可能是一個非常合理的選擇。

第二分支應該是完全沒有意義的,因為比較和分配可比的成本。 也就是說,您可以在鏈接中看到,如果編譯器具有對變量的引用,則仍然不會執行此優化。 如果值是局部變量(如在演示的代碼中那樣),則編譯器將優化分支。

總之,第一段代碼可能是一個合理的優化,第二段,可能只是一個累了的程序員。

在許多情況下,使用已經存在的值寫入變量可能比讀取它更慢,找出已經保持所需的值,並跳過寫入。 某些系統具有處理器緩存,可立即將所有寫入請求發送到內存。 雖然這種設計在今天並不常見,但它們過去常常很常見,因為它們可以提供完整讀/寫緩存所能提供的大部分性能提升,但成本只是其中的一小部分。

像上面這樣的代碼也可以在某些多CPU情況下相關。 最常見的情況是在兩個或多個CPU核心上同時運行的代碼將重復命中變量。 在具有強大內存模型的多核緩存系統中,想要編寫變量的核心必須首先與其他核心協商以獲取包含它的緩存行的獨占所有權,然后必須再次協商以在下次放棄此類控制時任何其他核心都想讀或寫它。 這樣的操作往往非常昂貴,即使每次寫入只是存儲已經存儲的值,也必須承擔成本。 但是,如果位置變為零並且永遠不會再次寫入,則兩個內核可以同時保留高速緩存行以進行非獨占只讀訪問,並且永遠不必進一步協商它。

在幾乎所有多個CPU都可以命中變量的情況下,變量應該至少被聲明為volatile 可能適用的一個例外是,在main()啟動后發生的對變量的所有寫入都將存儲相同的值,並且無論一個CPU的任何存儲是否可見,代碼都將正常運行在另一個。 如果多次執行某些操作會浪費但是無害,並且變量的目的是說是否需要完成,那么許多實現可能能夠生成更好的代碼而沒有使用volatile限定符,只要它們沒有嘗試通過使寫入無條件來提高效率。

順便說一下,如果對象被經由指針訪問,就上面的代碼另一種可能的原因是:如果一個函數被設計成接受一個const對象,其中某一個領域是零,或一個非const ,其應該有場地對象設置為零,可能需要像上面這樣的代碼來確保兩種情況下定義的行為。

關注第一塊代碼:這是基於Chandler Carruth對Clang的推薦的微優化(參見此處獲取更多信息),但它並不一定認為它是這種形式的有效微優化(使用if if)比三元)或任何給定的編譯器。

Modulo是一個相當昂貴的操作,如果代碼經常執行並且有一個強大的統計傾向於條件的一側或另一側,CPU的分支預測(給定一個現代CPU)將顯着降低分支指令的成本。

對我來說使用if if似乎是一個壞主意。

你是對的。 無論idx >= idx_max ,它都將在idx %= idx_max之后的idx %= idx_max 如果idx < idx_max ,它將保持不變,是否遵循if。

雖然您可能認為圍繞模數進行分支可能會節省時間,但我認為,真正的罪魁禍首是,當遵循分支時,流水線化現代CPU必須重置其管道,並且這需要花費相對大量的時間。 最好不要跟隨分支,而不是整數模,這大約和整數除法一樣多。

編輯:事實證明,模數對分支的速度相當慢,正如其他人所建議的那樣。 這是一個研究這個問題的人: CppCon 2015:Chandler Carruth“調優C ++:基准測試,CPU和編譯器!哦,我的!” (在另一個SO問題中建議與此問題的另一個答案相關聯)。

這個人寫編譯器,並認為沒有分支會更快; 但他的基准證明他錯了。 即使分支機構僅占20%的時間,它的測試速度也更快。

沒有if的另一個原因是:維護一行代碼,讓別人弄清楚它意味着什么。 上面鏈接中的人實際上創建了一個“更快模數”的宏。 恕我直言,這個或內聯函數是性能關鍵型應用程序的方法,因為沒有分支,你的代碼將變得更容易理解,但執行速度會快。

最后,上面視頻中的人正計划讓編譯器編寫者知道這種優化。 因此,如果不在代碼中,可能會為您添加if。 因此,當出現這種情況時,僅僅是mod就能做到。

暫無
暫無

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

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