為什麼GNU grep如此之快?
編注:這是GNU grep的原作者Mike Haertel 在FreeBSD郵件列表中對 “GNU grep為什麼比BSD grep要快” 所做的回答,下面是郵件正文內容:
Gabor 您好,
我是GNU grep的原作者,同時也是一名FreeBSD使用者,不過我一直使用的是-stable版本(也就是更老的版本),而沒怎麼關注-current版本。
但是,當我無意間翻閱-current版的郵件列表時,偶然發現了一些關於BSD grep與GNU grep效能的討論,你可能也注意到了那些討論。
不管怎麼說,僅供參考吧,下面是一些簡單的總結,關於為什麼GNU grep如此之快。或許你能借鑑其中的一些思想運用到BSD grep中去。
#技巧1:GNU grep之所以快是因為它並不會去檢查輸入中的每一個位元組。
#技巧2:GNU grep之所以快是因為它對那些的確需要檢查的每個位元組都執行非常少的指令(操作)。
GNU grep使用了非常著名的Boyer-Moore演算法(譯者注:BM演算法,是一種非常高效的字串搜尋演算法,一般情況下,比KMP演算法快3-5倍,具體可檢視這篇講解非常詳細的文章:grep之字串搜尋演算法Boyer-Moore由淺入深(比KMP快3-5倍)),該演算法首先從目標字串的最後一個字元開始查詢,並且使用一個查詢表,它可以在發現一個不匹配字元之後,計算出可以跳過多少個輸入字元並繼續查詢。
GNU grep還展開了Boyer-Moore演算法的內部迴圈,並建立了一個Boyer-Moore的delta表,這樣它就不需要在每一個展開的步驟進行迴圈退出判斷了。這樣的結果就是,在極限情況下(in the limit),GNU grep在需要檢查的每一個輸入位元組上所執行的x86指令不會超過3條(並且還跳過了許多位元組)。
你可以看看由Andrew Hume和Daniel Sunday 1991年11月在“Software Practice & Experience”上發表的論文“Fast String Searching”,該文很好的討論了Boyer-Moore演算法的實現技巧,該文有免費的PDF線上版(譯者注:點這裡檢視或下載)。
一旦有了快速搜尋,這時你會發現也需要同樣快速的輸入。
GNU grep使用了原生Unix輸入系統呼叫並避免了在讀取後對資料進行拷貝。
而且,GNU grep還避免了對輸入進行分行,查詢換行符會讓grep減慢好幾倍,因為要找換行符你就必須檢視每個位元組!
所以GNU grep沒有使用基於行的輸入,而是將原資料讀入到一個大的緩衝區buffer,用Boyer-Moore演算法對這個緩衝區進行搜尋,只有在發現一個匹配之後才會去查詢最近的換行符(某些命令引數,比如-n會禁止這種優化)。
最後,當我還在維護GNU grep的時候(15+年前……),GNU grep也嘗試做一些非常困難的事情使核心也能避免處理輸入的每個位元組,比如使用mmap()而不是read()來進行檔案輸入。當時,用read()會使大部分Unix版本造成一些額外的拷貝。因為我已經不再GNU grep了,所以似乎mmap已經不再預設使用了,但是你仍然可以通過引數–mmap來啟用它,至少在檔案系統的buffer已經快取了你的資料的情況下,mmap仍然要快一些:
$ time sh -c 'find . -type f -print | xargs grep -l 123456789abcdef' real 0m1.530s user 0m0.230s sys 0m1.357s $ time sh -c 'find . -type f -print | xargs grep --mmap -l 123456789abcdef' real 0m1.201s user 0m0.330s sys 0m0.929s
[這裡使用的輸入是一個648M的MH郵件資料夾,包含大約41000條資訊]
所以即使在今天,使用–mmap仍然可以提速20%以上。
總結:
使用Boyer-Moore演算法(並且展開它的內層迴圈)。
使用原生系統呼叫來建立你的緩衝輸入,避免在搜尋之前拷貝輸入位元組。(無論如何,最好使用緩衝輸出,因為在grep的常用場景中,輸出的要比輸入的少,所以輸出緩衝拷貝的開銷要小,並且可以節省許多這樣小的無緩衝寫操作。)
在找到一個匹配之前,不要查詢換行符。
嘗試做一些設定(比如頁面對齊緩衝區,按頁大小來讀取塊,選擇性的使用mmap),這樣可以使核心避免拷貝位元組。
讓程式變得更快的關鍵就是讓它們做更少的事情。;-)
致禮
Mike
本文由 伯樂線上 - 敏敏 翻譯自 FreeBSD Mailing Lists
連結:http://blog。jobbole.com/52313/