Flex的正则表达式匹配速度与手工代码的比较
2015-04-30 20:19
246 查看
flex是一个词法分析器生成器,它是编译器和解释器编程人员的常用工具之一。flex的程序主要由一系列带有指令(称为动作代码)的正则表达式组成。在匹配输入时,flex会将所有的正则表达式翻译成确定性有穷自动机,这使得flex等词法分析器生成器生成的词法分析器匹配输入模式的效率非常高。当然,有人指责flex不够灵活,功能有限,很多问题都无法解决,比如Javascript、C++等语言中二义性的问题,实际上很多程序(比如Python的解释器)的词法分析器都是用的手工代码而不是flex自动生成的。这些都不在这篇文章的讨论范围内,我主要想通过对flex和手工写的C代码编译的一个词数统计程序(word counter)来非常粗略地评价一下flex匹配正则表达式的速度如何。
测试方法:分别运行用flex生的C代码和手工C代码编译的程序,设置三种不同大小的文件,用shell的time指令来测试两个程序运行所用的时间。
下面上源码,先是flex源码 flexwc.l:
编译指令:
下面是C代码,manwc.c:
编译指令:
下面进行第一次测试,指令及结果如下:
注意,因为flex的默认输入流和C代码的输入流都是stdin,所以这里用到了输入流重定向'<'。
下面进行第二次测试,指令及结果如下:
下面进行第三次测试,指令及结果如下:
(此结果受机器影响较大,仅作为参考)
经过三次规模从小到大的测试,可以看到用flex生成的词法分析器匹配正则表达式的速度几乎总是比手工C代码要快。可以预见,当模式变得更为复杂时,flex生成的代码的执行速度将会比纯手工C代码的效率高更多。这是由于flex处理正则表达式的内部格式(也就是确定性有穷自动机)使得匹配正则表达式时几乎与问题规模无关(也就是说并不是模式越复杂匹配的时间就会越久,但是也会有例外),而用手工的C代码处理此类问题时,总是倾向于将字符流一步步的进行分析,比如分析C语言中的'/'符号,当读入第一个'/'时,并不能确定到底是什么(有二义性),只有继续读接下来的一个字符:如果是一个数字,那'/'就是除法运算符;如果是‘/’那就是一个行注释,可以直接忽略本行余下的内容了;如果是'*'那还要判断什么位置出现了"*/",然后忽略中间的注释,如果找不到匹配的"*/",就需要报错。flex允许你对这三种方式定义明确的正则表达式:"/",“//”,“/*”(最后一种匹配/**/注释的情况还要用到起始状态的知识,这里就不介绍了)。总之记住一点:一次匹配一大段几乎总是比每次匹配一个字符、匹配多次要快,当然也有例外。
总结:flex作为一种格式化文件处理工具,用它生成的代码不仅可以用于编译器解释器的编程人员的词法分析器开发,还可用于所有需要进行分析的格式化文件,比如快速查找文件中的某一特定格式、自动排版、代码自动缩进、语法着色等等,一切就看你能做什么了!
测试方法:分别运行用flex生的C代码和手工C代码编译的程序,设置三种不同大小的文件,用shell的time指令来测试两个程序运行所用的时间。
下面上源码,先是flex源码 flexwc.l:
/*用flex实现的一个词数统计程序。 flexwc.l */ %{ /*定义全局变量,分别统计字符数,单词数和行数*/ int chars=0; int words=0; int lines=0; %} %% [a-zA-Z]+ { ++words; chars+=strlen(yytext);}/*正则表达式匹配任意单词,用字符串函数统计输入流中的当前字符数*/ \n { ++chars; ++lines; }/*遇到换行符,新的一行开始*/ . {++chars;}/*其它字符*/ %% main(int argc,char **argv) { yylex();/*调用flex生成的例程*/ printf("%8d%8d%8d\n",lines,words,chars);/*打印行号,单词数,字符数*/ }
编译指令:
$flex flexwc.l $gcc -o flexwc -O3 flexwc
下面是C代码,manwc.c:
/*手工C代码的词数统计程序 manwc.c */ #include <stdio.h> #include <ctype.h>/*使用isalpha()*/ /*全局变量,分别记录文件的字符数,行数和单词数*/ int i_char=0,i_line=0,i_word=0; int main(void) { int inword=0; /*当inword==1时说明正在处理一个单词的内部(也就是一个单词还没结束) 否则意味一个单词的结束 */ char ch; while ((ch=getchar())!=EOF)/*文件未结束*/ { ++i_char;/*增加字符数*/ if ('\n'==ch)/*行数*/ ++i_line; if (isalpha(ch) && !inword)/*一个单词的开始*/ { inword=1; ++i_word; } if (!isalpha(ch) && inword)/*一个单词的结束*/ { inword=0; } } printf("%8d%8d%8d\n",i_line,i_word,i_char);/*打印文件行数、单词数、字符数*/ return 0; }
编译指令:
$gcc -o manwc -O3 manwc.c
下面进行第一次测试,指令及结果如下:
$time ./autowc < foo.txt 4 0 7 real 0m0.014s user 0m0.000s sys 0m0.000s $ time ./manwc < foo.txt 4 0 7 real 0m0.024s user 0m0.000s sys 0m0.000s
注意,因为flex的默认输入流和C代码的输入流都是stdin,所以这里用到了输入流重定向'<'。
下面进行第二次测试,指令及结果如下:
$time ./autowc < lex.yy.c 1823 6705 45887 real 0m0.008s user 0m0.004s sys 0m0.000s $ time ./manwc < lex.yy.c 1823 6705 45887 real 0m0.008s user 0m0.004s sys 0m0.000s
下面进行第三次测试,指令及结果如下:
$time ./autowc < MAINTAINERS 9721 46364 269584 real 0m0.013s user 0m0.012s sys 0m0.000s $ time ./manwc < MAINTAINERS 9721 46364 269584 real 0m0.019s user 0m0.020s sys 0m0.000s
(此结果受机器影响较大,仅作为参考)
经过三次规模从小到大的测试,可以看到用flex生成的词法分析器匹配正则表达式的速度几乎总是比手工C代码要快。可以预见,当模式变得更为复杂时,flex生成的代码的执行速度将会比纯手工C代码的效率高更多。这是由于flex处理正则表达式的内部格式(也就是确定性有穷自动机)使得匹配正则表达式时几乎与问题规模无关(也就是说并不是模式越复杂匹配的时间就会越久,但是也会有例外),而用手工的C代码处理此类问题时,总是倾向于将字符流一步步的进行分析,比如分析C语言中的'/'符号,当读入第一个'/'时,并不能确定到底是什么(有二义性),只有继续读接下来的一个字符:如果是一个数字,那'/'就是除法运算符;如果是‘/’那就是一个行注释,可以直接忽略本行余下的内容了;如果是'*'那还要判断什么位置出现了"*/",然后忽略中间的注释,如果找不到匹配的"*/",就需要报错。flex允许你对这三种方式定义明确的正则表达式:"/",“//”,“/*”(最后一种匹配/**/注释的情况还要用到起始状态的知识,这里就不介绍了)。总之记住一点:一次匹配一大段几乎总是比每次匹配一个字符、匹配多次要快,当然也有例外。
总结:flex作为一种格式化文件处理工具,用它生成的代码不仅可以用于编译器解释器的编程人员的词法分析器开发,还可用于所有需要进行分析的格式化文件,比如快速查找文件中的某一特定格式、自动排版、代码自动缩进、语法着色等等,一切就看你能做什么了!
相关文章推荐
- 比较常用的几个正则表达式(匹配数字)
- 19.Scala中的正则表达式、与模式匹配结合的Reg代码实战
- 正则表达式匹配(URL、电话、手机、邮箱)的实例代码
- 代码之美 短小精悍的正则表达式匹配器
- 比较常用的几个正则表达式(匹配数字)
- 比较常用的几个正则表达式(匹配数字)
- Java代码:使用正则表达式匹配电子邮箱地址
- 基于正则表达式匹配的CSS语法高亮及代码格式化
- 关于flex中正则表达式上下文匹配的问题
- 五周二次课(11月14日) 11.1 常用正则表达式 11.2 re正则对象和正则匹配效率比较 11.3 编译正则对象
- 实例代码详解正则表达式匹配换行
- xpath与正则表达式抽取网页信息的速度比较
- 用NFA实现正则表达式匹配(java代码)
- 分别匹配电话、手机、邮箱和网址的比较强大正则表达式
- 使用正则表达式匹配JS函数代码
- C#正则表达式匹配HTML中的图片路径,图片地址代码
- lintcode 正则表达式匹配 ac代码
- 正则表达式匹配路由的实现代码
- 常用正则表达式匹配代码介绍
- 代码之美(试读)-第1章 正则表达式匹配器