簡體   English   中英

clang vs gcc-優化包括運算符new

[英]clang vs gcc - optimization including operator new

我有一個要測試的簡單示例,我注意到當涉及到new運算符時,gcc優化(-O3)似乎不如clang優化。 我想知道可能是什么問題,是否有可能迫使gcc以某種方式產生更優化的代碼?

template<typename T>
T* create() { return new T(); }

int main() {
    auto result = 0;
    for (auto i = 0; i < 1000000; ++i) {
        result += (create<int>() != nullptr);
    }

    return result;
}


#clang3.6++ -O3 -s --std=c++11 test.cpp
#size a.out
   text    data     bss     dec     hex filename
   1324     616       8    1948     79c a.out
#time ./a.out 
real 0m0.002s
user 0m0.001s
sys  0m0.000s

#gcc4.9 -O3 -s --std=c++11 test.cpp
#size a.out
   text    data     bss     dec     hex filename
   1484     624       8    2116     844 a.out
#time ./a.out
real 0m0.045s
user 0m0.035s
sys  0m0.009s

上面的示例只是我一開始就測試過的代碼的簡單版本,但仍然說明了gcc / clang之間的區別。 我也檢查了匯編代碼,大小沒有太大的區別,但是性能上肯定沒有太大的區別。 另一方面,也許lang正在做不允許的事情?

如果將這段代碼插入Godbolt中,我們可以看到clang將代碼優化為:

main:                                   # @main
movl    $1000000, %eax          # imm = 0xF4240
ret

gcc不會執行此優化。 那么問題是有效的優化嗎? 這是否遵循as-if rule ,該as-if ruleC ++標准草案 1.9 程序執行部分中指出( 強調我的意思 ):

本國際標准中的語義描述定義了參數化的不確定性抽象機器。 本國際標准對一致性實現的結構沒有要求。 特別是,它們不需要復制或模擬抽象機的結構。 相反, 需要遵循的實現來(僅)模擬抽象機的可觀察行為,如下所述。 5

注釋5表示:

該規定有時被稱為“假設”規則 ,因為只要可以從可觀察到的行為中確定結果,就可以無視本國際標准的任何要求,而該實現可以自由地執行。該程序。 例如,如果實際實現可以推斷出未使用其值並且不會產生影響程序可觀察行為的副作用,則無需評估表達式的一部分。

由於new可能會引發異常,因此它具有可觀察到的行為,因為它將更改程序的返回值。

R.MartinhoFernandes認為,何時拋出異常是實現細節,因此clang可以確定這種情況不會導致異常,因此隱藏new調用不會違反as-if rule 在我看來,這似乎是一個合理的論點。

但正如TC指出的那樣:

可以在其他翻譯單元中定義新的替換全局運算符new

Casey提供了一個示例,該示例顯示即使當clang看到有替代品時,即使失去了副作用,它仍會執行此優化。 因此,這似乎是過於激進的優化。

注意, 內存泄漏不是未定義的行為

理由是,沒有關於機器可能擁有多少內存的規則,該語言也沒有提供任何方法來檢查已分配或可用的內存量(盡管請注意,POSIX確實定義了mallinfo)。 在這里,我們在具有無限內存機器的抽象機器上模擬您的程序,其中連續不斷地分配成功。 或至少是為了此循環中的分配目的而使用無限內存,但對於整個程序而言並不一致。 無論如何,我知道對此有兩個反對意見。

首先,考慮是否是malloc而不是new運算符。 C99規范指出:

malloc函數為大小由大小指定且值不確定的對象分配空間。 malloc函數返回空指針或指向已分配空間的指針。

將malloc()編譯為始終成功似乎符合該規范。 但是,如果您多次調用它而不是我們實際為其創建一個指針,並且僅在失敗后退出循環,該怎么辦? 一種可能的解決方法是,在抽象機定義中沒有規則規定64位指針只能容納2 64個可能的值,只是沒有提供構造該范圍之外的值的方法。 看來實現可能會隨意創建這樣的東西。 我個人認為該答案不令人滿意。

考慮我們還優化了諸如"T *t1 = new T; T *t2 = (T*)rand();" 通過假設t1可以不別名t2 無論rand是否選擇了正確的地址,還是遍歷整個地址空間都沒有關系,一旦我們證明t1的地址沒有饋入t2,我們就可以得出結論,它們引用了不同的對象。 盡管我希望這成為標准的工作方式,而這就是編譯器的工作方式,但我不知道有任何支持該立場的標准。 這很可能成為未來論文的主題。

其次,運算符new不是malloc,它是可替換的函數。 正如Casey的回復中所建議的那樣,我們打算遵循N3664中的規則(盡管我認為clang對待new表達式的方式與顯式調用operator new的方式不同)。 Shafik指出這似乎違反了因果關系,但N3664最初是N3433 ,我敢肯定,我們先寫了優化程序,然后寫了論文。

似乎clang正在根據合並到C ++ 14中的N3664 Clarifying Memory Allocation中的更改規則來優化內存分配。 N3664可以通過合並分配或完全消除分配來減少對分配/解除分配功能的調用次數。

暫無
暫無

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

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