Flex的正則表達式匹配速度與手工代碼的比較

  flex是一個詞法分析器生成器,它是編譯器和解釋器編程人員的經常使用工具之一。flex的程序主要由一系列帶有指令(稱爲動做代碼)的正則表達式組成。在匹配輸入時,flex會將全部的正則表達式翻譯成肯定性有窮自動機,這使得flex等詞法分析器生成器生成的詞法分析器匹配輸入模式的效率很是高。固然,有人指責flex不夠靈活,功能有限,不少問題都沒法解決,好比Javascript、C++等語言中二義性的問題,實際上不少程序(好比Python的解釋器)的詞法分析器都是用的手工代碼而不是flex自動生成的。這些都不在這篇文章的討論範圍內,我主要想經過對flex和手工寫的C代碼編譯的一個詞數統計程序(word counter)來很是粗略地評價一下flex匹配正則表達式的速度如何。正則表達式

  測試方法:分別運行用flex生的C代碼和手工C代碼編譯的程序,設置三種不一樣大小的文件,用shell的time指令來測試兩個程序運行所用的時間。shell

  下面上源碼,先是flex源碼 flexwc.l:編程

 1 /*用flex實現的一個詞數統計程序。
 2     flexwc.l
 3 */
 4 
 5 %{
 6 /*定義全局變量,分別統計字符數,單詞數和行數*/
 7 int chars=0;
 8 int words=0;
 9 int lines=0;
10 %}
11 %%
12 [a-zA-Z]+    { ++words; chars+=strlen(yytext);}/*正則表達式匹配任意單詞,用字符串函數統計輸入流中的當前字符數*/
13 \n            { ++chars; ++lines; }/*遇到換行符,新的一行開始*/
14 .            {++chars;}/*其它字符*/            
15 %%
16 
17 main(int argc,char **argv)
18 {
19     yylex();/*調用flex生成的例程*/
20     printf("%8d%8d%8d\n",lines,words,chars);/*打印行號,單詞數,字符數*/
21 }

  編譯指令:函數

1 $flex flexwc.l
2 $gcc -o flexwc -O3 flexwc

  下面是C代碼,manwc.c:工具

 1 /*手工C代碼的詞數統計程序
 2     manwc.c
 3 */
 4 
 5 #include <stdio.h>
 6 #include <ctype.h>/*使用isalpha()*/
 7 
 8 /*全局變量,分別記錄文件的字符數,行數和單詞數*/
 9 int i_char=0,i_line=0,i_word=0;
10 
11 int main(void)
12 {
13     int inword=0;
14     /*當inword==1時說明正在處理一個單詞的內部(也就是一個單詞還沒結束)
15     不然意味一個單詞的結束    */
16     char ch;
17     while ((ch=getchar())!=EOF)/*文件未結束*/
18     {
19         ++i_char;/*增長字符數*/
20         if ('\n'==ch)/*行數*/
21             ++i_line;
22         if (isalpha(ch) && !inword)/*一個單詞的開始*/
23         {
24             inword=1;
25             ++i_word;
26         }
27         if (!isalpha(ch) && inword)/*一個單詞的結束*/
28         {
29             inword=0;    
30         }
31     }
32     printf("%8d%8d%8d\n",i_line,i_word,i_char);/*打印文件行數、單詞數、字符數*/
33     return 0;
34 }

  編譯指令:測試

1 $gcc -o manwc -O3 manwc.c

  下面進行第一次測試,指令及結果以下:flex

 1 $time ./autowc < foo.txt
 2        4       0       7
 3 
 4 real    0m0.014s
 5 user    0m0.000s
 6 sys    0m0.000s
 7 
 8 $ time ./manwc < foo.txt
 9        4       0       7
10 
11 real    0m0.024s
12 user    0m0.000s
13 sys    0m0.000s

注意,由於flex的默認輸入流和C代碼的輸入流都是stdin,因此這裏用到了輸入流重定向'<'。spa

  下面進行第二次測試,指令及結果以下:翻譯

 1 $time ./autowc < lex.yy.c 
 2     1823    6705   45887
 3 
 4 real    0m0.008s
 5 user    0m0.004s
 6 sys    0m0.000s
 7 
 8 $ time ./manwc < lex.yy.c 
 9     1823    6705   45887
10 
11 real    0m0.008s
12 user    0m0.004s
13 sys    0m0.000s

  下面進行第三次測試,指令及結果以下:code

 1 $time ./autowc < MAINTAINERS 
 2     9721   46364  269584
 3 
 4 real    0m0.013s
 5 user    0m0.012s
 6 sys    0m0.000s
 7 
 8 $ time ./manwc < MAINTAINERS 
 9     9721   46364  269584
10 
11 real    0m0.019s
12 user    0m0.020s
13 sys    0m0.000s

   (此結果受機器影響較大,僅做爲參考)

  通過三次規模從小到大的測試,能夠看到用flex生成的詞法分析器匹配正則表達式的速度幾乎老是比手工C代碼要快。能夠預見,當模式變得更爲複雜時,flex生成的代碼的執行速度將會比純手工C代碼的效率高更多。這是因爲flex處理正則表達式的內部格式(也就是肯定性有窮自動機)使得匹配正則表達式時幾乎與問題規模無關(也就是說並非模式越複雜匹配的時間就會越久,可是也會有例外),而用手工的C代碼處理此類問題時,老是傾向於將字符流一步步的進行分析,好比分析C語言中的'/'符號,當讀入第一個'/'時,並不能肯定究竟是什麼(有二義性),只有繼續讀接下來的一個字符:若是是一個數字,那'/'就是除法運算符;若是是‘/’那就是一個行註釋,能夠直接忽略本行餘下的內容了;若是是'*'那還要判斷什麼位置出現了"*/",而後忽略中間的註釋,若是找不到匹配的"*/",就須要報錯。flex容許你對這三種方式定義明確的正則表達式:"/",「//」,「/*」(最後一種匹配/**/註釋的狀況還要用到起始狀態的知識,這裏就不介紹了)。總之記住一點:一次匹配一大段幾乎老是比每次匹配一個字符、匹配屢次要快,固然也有例外。

  總結:flex做爲一種格式化文件處理工具,用它生成的代碼不只能夠用於編譯器解釋器的編程人員的詞法分析器開發,還可用於全部須要進行分析的格式化文件,好比快速查找文件中的某一特定格式、自動排版、代碼自動縮進、語法着色等等,一切就看你能作什麼了!

相關文章
相關標籤/搜索