近日在一次測試Linux內核路由查找算法的過程中,發現一個printf語句竟然能將性能降低2/3。當然,使用“竟然”一詞並不意味著這個問題是第一次發現,我的想法是,把它記錄下來,讓沒有經驗的同學對printf知其所以然,同時導出我對“性能攸關”的這類算法中記錄日志的一個觀點。
聲明
我不會把大段的源代碼貼在文章中,而只是希望能通過闡述原理把我的意思表達清楚。誠然,作為程序員沒有代碼好像一切都會很虛,不過同樣的,也是因為代碼,總是會把人逼進死胡同,代碼只是一種實現,理解了原理,作為一個懂編程的程序員,任何人都可以寫出一個自己的實現。
我會給出原理圖,但是這圖決不是我憑空想象的,來源在哪?當然是UNIX的相關標准以及Linux的具體實現代碼。既然原理來自於Linux的代碼,為何不貼出來分析一下呢?要知道,代碼隨著Linux的內核版本,C庫的版本以及應用程序的版本變化而變化,不變的是思想!UNIX歷經幾十年,其思想不還在指導著千千萬萬的程序員嗎?另外,有誰會去通讀Linux內核代碼呢?對於大多數的人而言,如果想知道printf或者任何其它的接口的原理,肯定不會去擺開架勢做出一副要先了解Linux內核架構,C庫架構作為前置知識,然後去跟蹤調試其實現。在以上這個過程中,你會把大量的精力消耗在理解不相關的內容上,比如函數調用關系,層層嵌套的條件語句,或者調試器怎麼使用,諸如此類。
關於printf
printf是一個接口,跟UNIX標准IO的write系統調用類似,但是更像C庫的fwrite,因為同系列的函數中還有一個fprintf(至於同系列其它的函數,請自行man)。printf和fwrite的區別在於兩點:
1.它可以格式化輸出,如果用fwrite,它接受的是一個固定的buffer,你不得不在調fwrite之前先使用sprintf之類的函數格式化buffer;
2.它免除了你的fopen-fwrite-fclose這個序列的調用,因為它直接將格式化的內容寫入UNIX進程自然打開的1號文件描述符,即標准輸出。
既然printf寫入了標准輸出,那麼接下來就要定義什麼是標准輸出。在早期UNIX年代,人們在終端或者偽終端操作機器,那時的輸入基本都是鍵盤,磁帶更古老的東西,而輸出就是一個計算結果,需要展示出來給人看的那種,一般為終端屏幕,也可以是一條紙帶,那麼程序怎麼知道輸入和輸出到底是什麼呢?這就需要程序明確指定。UNIX的“一切皆文件”思想以及“分離抽象”思想徹底改變了這一切。
UNIX定義了抽象文件描述符0,1,2分別為標准輸入,標准輸出,標准錯誤輸出。至於它們到底對應什麼設備,你可以在程序初始化的時候顯式重定向到任意設備,也可以在外部shell做類似的重定向,這樣就把指明設備這件事從程序分離了出來。
我為什麼不統一說一下fwrite調用對程序性能的影響呢?因為該調用之前你必須執行fopen,而fopen的一個參數明確表示了你希望寫入的對象是什麼,這就不會帶來異議,畢竟如果你非要在性能測試的時候寫CF卡,那也是你願意。printf就不同了,它對效率的影響取決於標准輸出是什麼以及你是如何重定向標准輸出的,所謂的標准輸出並不是真實的設備,它只是一個抽象層,具體如何解釋標准輸出,還要依靠外部。
數據都去哪兒了
我以下面這個超級小的程序來說明printf的時候,數據都去哪了:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int i = 0;
int c = atoi(argv[1]);
for(; i < c; i++) {
printf("############ %d\n", i);
}
return 0;
}
更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2014-04/100907p2.htm