2017-06-07 100 views
2

這裏是我的測試代碼:爲什麼GCC沒有將這個'printf'優化爲'puts'?

#include<stdio.h> 

static inline void foo(int a){ 
    printf("%x\n", a); 
} 

int main(void){ 
    foo(0x1234);  
    return 0; 
} 

我想GCC應該認識到,a是一個文字整數,優化像這樣的代碼:

puts("1234"); 

,但我得到了下面的彙編代碼:

│0x8048341 <main+17>  push $0x1234           
│0x8048346 <main+22>  push $0x80484e0         
│0x804834b <main+27>  push $0x1           
│0x804834d <main+29>  call 0x8048310 <[email protected]> 

我的項目中存在很多這樣的代碼,因爲我一直認爲GCC會爲我優化,甚至在某些情況下可以簡單使用'write()'的文本,我堅持使用printf,因爲我認爲我會從緩衝機制中獲益。

現在我覺得很後悔,對削減格式字符串會殺死任何增益我的開銷。我的項目中的這些代碼相當低級,可能會導致性能瓶頸。

+1

[過早優化是萬惡之源](http://c2.com/cgi/wiki?PrematureOptimization) – Barmar

+0

你有沒有*測量過*的表現? – Caleth

+3

依靠編譯器神奇地爲你優化的東西通常是一個絕望的做法。 – VTT

回答

6

這些代碼在我的項目中是相當低級的,它們可能會導致性能瓶頸。

首先,我可以平息你的擔心,這是不可能的。控制檯的開銷I/O是巨大(相對而言),所以總是會在你的代碼中的瓶頸,不管用什麼方法,你用它來做到這一點。

我想GCC應該認識到,a是一個文字整數,優化像這樣的代碼:

puts("1234"); 

顯然事實並非如此。海灣合作委員會(和鐺)does perform an optimization where printf("...\n"); is transformed into puts("...");,你可以看到here,但是當你使用字符串文字printf這只是發生。優化器不會(當前)查看格式字符串,解析它並對其進行優化。你叫printf,所以你得到printf

編譯器優化不保證,所以你不應該寫代碼,依賴於他們,但未事先驗證所需的優化其實下你感興趣(包括代碼的變化情況都被應用,編譯器版本,目標平臺等)。

如果您想建議此作爲GCC優化一個可能的改進,你可以建議在their Bugzilla的增強。但不要屏住呼吸,很快就會實施。實施這種優化所需的邏輯並不值得付出努力,因爲考慮到可能預期的實際性能改進最少(參見上文)。

在此期間,如果你絕對需要這種優化以最小的改動你的代碼,那麼你可以使用some macro hackery

#define STRINGIFY_INTERNAL(x) #x 
#define STRINGIFY(x)   STRINGIFY_INTERNAL(x) 

#define foo(a)     puts(STRINGIFY(a)) 

這確實產生所需的輸出:

.LC0: 
     .string "0x1234" 
MyFunction: 
     sub  esp, 24 
     push OFFSET FLAT:.LC0 
     call puts 
     xor  eax, eax 
     add  esp, 28 
     ret 
+0

原來,鏗鏘會優化'const char fmt [] = = { 'h','i','\ n',0};'/'printf(fmt);'放入'puts'中。 https://godbolt.org/g/aTQRKX但是對於gcc,它確實必須是字符串文字。 (至少間接地;'const char * fmt =「hi \ n」;'然後將其傳遞給printf進行優化,只要沒有其他參數用於printf。) –

+0

有趣的觀察,@Peter。我沒有考慮過這種變化,我真的很驚訝GCC的模式匹配優化在那裏不起作用。 –

+0

我也很驚訝,但它有道理。我猜它不會將char數組或它們的初始化方法看作字符串常量,因爲它必須檢查它們最後只包含一個「0」字節。您可以在asm輸出中看到這一點,其中gcc使用單獨的'.byte'指令而不是'.asciz',但clang注意到類似字符串的特性併發出'.asciz'以及執行puts優化。 –

1

符合標準的庫實現可以包含超出標準定義的函數,這將改變標準函數的行爲方式。例如,庫可能包含一個__select_alternate_digits函數,該函數在調用時將導致後續調用printf以使用非正常數字顯示數字。

利用這樣的庫,給定的代碼:

#include <stdio.h> // Could legitimately include functions that aren't 
        // defined by the Standard, but which start with __. 

int main(void) 
{ 
    __select_alternate_digits("⁰¹²³⁴⁵⁶⁷⁸⁹"); 
    printf("%d",123); 
    __select_alternate_digits(0); // Reset to default set  
} 

調用__select_alternate_digits可能導致上面的程序,以輸出「¹²³」,而不是「123」。如果編譯器捆綁了自己的printf函數,它可能知道它的行爲不會受到任何其他函數調用的影響。但是,如果它使用外部庫,那麼除非程序完全沒有任何對函數的調用,否則編譯器應該認爲這些函數可能具有編譯器無法預測的效果。

+0

這並不能解釋任何事情:'printf(「123 \ n」);'[用_ puts調用替換](https://godbolt.org/g/Gr9uqZ),而OP的代碼isn儘管原始函數是同一個:'printf',所以編譯器確實知道它的一些事情。 – Ruslan

+0

@Ruslan:對於printf的實現來說,使用不包含'%'的字符串進行任何類型的翻譯是很常見的。它認爲海灣合作委員會可能在假定他們不會的時候有點冒失,但是它進一步進入非平凡格式說明者領域時,出現問題的可能性就越大。但是,我確實忘了另一點:使用包含非平凡格式字符串的printf的代碼之一是* test * printf實現。如果程序員編寫'printf(「Hello」);''很可能程序員對輸出'Hello \ n'比感興趣...... – supercat

+0

...測試'printf'是否可以正確處理這些字符串。但是給定'printf(「%d \ n」, - 2147483647-1);'程序員可能不想輸出'-2147483648 \ n',而是確認特定的'printf'實現處理'INT_MIN'的情況正確。讓編譯器自己做格式化會破壞這個目標。 – supercat