pyre2比内置的re模块慢吗?

uke*_*ssi 4 python cython re2

使用pyre2https://github.com/axiak/pyre2)时,我遇到了性能问题(匹配时间)。

我有三个程序:

  1. 使用内置re模块的纯Python:https : //gist.github.com/1873402

  2. 使用Pyre2的Python:https://gist.github.com/1873402 。(代码的大部分与1号程序相同。除了使用内置re之外,它会将utf-8字符串解码为unicode,使用pyre2时则不需要)

  3. 使用re2的C / C ++:https//gist.github.com/1873417

我测量了两个时间:正则表达式预编译时间和匹配时间。

  • 1号程序:1.65s 1.25s

  • 2号程序:0.04s 1.8s

  • 3号程序:0.02s 0.8s

它们都使用相同的正则表达式和输入。(所有正则表达式均受支持re2

然后,我遵循了有关Cython中性能分析的文档。得到以下结果:

ncalls tottime percall cumtime percall filename:lineno(function)
   652884 16.477 0.000 25.349 0.000 re2.pyx:394(_search)
     9479 6.059 0.001 41.806 0.004 export_plain.py:60(匹配)
   652884 4.243 0.000 33.602 0.000 {搜索're2.Pattern'对象的方法'
   652884 4.010 0.000 29.359 0.000 re2.pyx:442(搜索)
   652884 3.056 0.000 3.056 0.000 re2.pyx:114(__ init__)
   652953 2.145 0.000 2.145 0.000 {实例}
   652884 2.002 0.000 2.002 0.000 re2.pyx:123(__ dealloc__)
   652953 1.911 0.000 1.911 0.000 re2.pyx:75(unicode_to_bytestring)
   652953 1.902 0.000 1.902 0.000 re2.pyx:86(pystring_to_bytestring)
        1 0.330 0.330 42.492 42.492 export_plain.py:98(export_fname)
     9479 0.173 0.000 0.173 0.000 {内置方法子}
    10000 0.120 0.000 0.120 0.000 {'str'对象的'split'方法}
     8967 0.063 0.000 0.099 0.000 re2.pyx:801(获取)
    10069 0.061 0.000 0.061 0.000 {'str'对象的方法'strip'}
       69 0.043 0.001 0.146 0.002 re2.pyx:806(准备模式)
     9036 0.038 0.000 0.038 0.000 re2.pyx:788(__下一个)
       69 0.022 0.000 0.169 0.002 re2.pyx:905(_compile)
        1 0.005 0.005 0.177 0.177 export_plain.py:36(加载)
       69 0.002 0.000 0.003 0.000 re2.pyx:784(__ init__)
       69 0.001 0.000 0.170 0.002 re2.pyx:763(编译)
       38 0.001 0.000 0.001 0.000 {'写入''文件'对象的方法}
       69 0.001 0.000 0.171 0.002 {re2.compile}
        1 0.001 0.001 42.669 42.669 export_plain.py:160(main)
        3 0.000 0.000 0.000 0.000 {打开}
       69 0.000 0.000 0.000 0.000 {“附加”“列表”对象的方法}
       19 0.000 0.000 0.000 0.000 {'str'对象的方法'join'}
        1 0.000 0.000 0.000 0.000通用路径.py:38(isdir)
        1 0.000 0.000 42.669 42.669 export_plain.py:153(run_re2_test)
        1 0.000 0.000 0.000 0.000 {posix.stat}
        4 0.000 0.000 0.000 0.000 {time.time}
        1 0.000 0.000 0.000 0.000 posixpath.py:59(加入)
        1 0.000 0.000 42.670 42.670:1()
        1 0.000 0.000 0.000 0.000 {'unicode'对象的方法'encode'}
        3 0.000 0.000 0.000 0.000 {'str'对象的'rfind'方法}
        2 0.000 0.000 0.000 0.000 posixpath.py:109(基本名称)
        1 0.000 0.000 0.000 0.000 posixpath.py:117(目录名)
        1 0.000 0.000 0.000 0.000状态.py:40(S_ISDIR)
        2 0.000 0.000 0.000 0.000 {len}
        1 0.000 0.000 0.000 0.000 {'列表'对象的方法'扩展'}
        1 0.000 0.000 0.000 0.000 {'str'对象的'startswith'方法}
        1 0.000 0.000 0.000 0.000 {'str'对象的'endswith'方法}
        1 0.000 0.000 0.000 0.000 stat.py:24(S_IFMT)
        1 0.000 0.000 0.000 0.000 {'file'对象的'__enter__'方法}
        1 0.000 0.000 0.000 0.000 {'_lsprof.Profiler'对象的方法'禁用'}

似乎_search函数(re2.pyx:393)占用了太多时间。但是我不认为这个版本与纯C版本之间有何不同。

PS:Pyre2修订版:提交 543f228

修订版2:变更集: 79:0c439a6bd795


我猜实际的Match功能(re2.pyx:424)大部分时间都花在了这个功能上。

然后,将Match函数重构为cdef函数,_my_match以便可以在概要文件结果中看到它,也重构StringPiece为cdef函数的分配_alloc_sp。(修改细节:https : //gist.github.com/1873993)重新配置它,然后得到:

2012年2月20日星期一20:52:47 Profile.prof

         3975043函数在28.265 CPU秒内调用

   排序:内部时间

   ncalls tottime percall cumtime percall filename:lineno(function)
   652884 10.060 0.000 20.230 0.000 re2.pyx:452(搜索)
   652884 4.131 0.000 28.201 0.000 {'re2.Pattern'对象的方法'搜索'}
   652884 3.647 0.000 3.647 0.000 re2.pyx:394(_my_match)
     9479 3.037 0.000 31.238 0.003 export_plain.py:62(匹配)
   652884 2.901 0.000 2.901 0.000 re2.pyx:443(_alloc_sp)
   652953 1.814 0.000 1.814 0.000 re2.pyx:86(pystring_to_bytestring)
   652953 1.808 0.000 1.808 0.000 re2.pyx:75(unicode_to_bytestring)
        1 0.332 0.332 31.926 31.926 export_plain.py:96(export_fname)
     9479 0.169 0.000 0.169 0.000 {内置方法子}
    10000 0.122 0.000 0.122 0.000 {'str'对象的'split'方法}
     8967 0.065 0.000 0.099 0.000 re2.pyx:849(获取)
    10069 0.064 0.000 0.064 0.000 {'str'对象的方法'strip'}
       69 0.042 0.001 0.142 0.002 re2.pyx:854(prepare_pattern)
     9036 0.035 0.000 0.035 0.000 re2.pyx:836(__下一个)
       69 0.023 0.000 0.166 0.002 re2.pyx:953(_compile)
        1 0.003 0.003 32.103 32.103 export_plain.py:158(main)
        1 0.003 0.003 0.174 0.174 export_plain.py:36(load)
       69 0.002 0.000 0.168 0.002 re2.pyx:811(编译)
       38 0.001 0.000 0.001 0.000 {'写入''文件'对象的方法}
       69 0.001 0.000 0.169 0.002 {re2.compile}
       69 0.001 0.000 0.001 0.000 re2.pyx:832(​​__ init__)
        1 0.001 0.001 32.104 32.104 export_plain.py:151(run_re2_test)
        1 0.000 0.000 32.105 32.105:1()
        2 0.000 0.000 0.000 0.000 {len}
        3 0.000 0.000 0.000 0.000 {打开}
        1 0.000 0.000 0.000 0.000 {'列表'对象的方法'扩展'}
       69 0.000 0.000 0.000 0.000 {实例}
       69 0.000 0.000 0.000 0.000 {“附加”“列表”对象的方法}
       19 0.000 0.000 0.000 0.000 {'str'对象的方法'join'}
        4 0.000 0.000 0.000 0.000 {time.time}
        1 0.000 0.000 0.000 0.000 {'unicode'对象的方法'encode'}
        1 0.000 0.000 0.000 0.000 posixpath.py:59(加入)
        1 0.000 0.000 0.000 0.000 {posix.stat}
        1 0.000 0.000 0.000 0.000通用路径.py:38(isdir)
        2 0.000 0.000 0.000 0.000 posixpath.py:109(基本名称)
        3 0.000 0.000 0.000 0.000 {'str'对象的'rfind'方法}
        1 0.000 0.000 0.000 0.000 posixpath.py:117(目录名)
        1 0.000 0.000 0.000 0.000状态.py:40(S_ISDIR)
        1 0.000 0.000 0.000 0.000 {'str'对象的'startswith'方法}
        1 0.000 0.000 0.000 0.000 {'str'对象的'endswith'方法}
        1 0.000 0.000 0.000 0.000 {'file'对象的'__enter__'方法}
        1 0.000 0.000 0.000 0.000 stat.py:24(S_IFMT)
        1 0.000 0.000 0.000 0.000 {'_lsprof.Profiler'对象的方法'禁用'}

但是search仍然要花费很多时间(tttime中为10.060)。

任何人都可以找出问题所在吗?

tom*_*mas 5

好吧,这取决于... pyre2渐近更快,但不一定每一个都更快特定的正则表达式。原因是re2会生成NFA并同时进入所有活动状态。re(如果我是正确的话)一次仅尝试NFA中的一条路径,如果失败,则会回溯并尝试另一条路径。这意味着re可以做各种花哨的东西,例如前瞻等等,因为它总是记住与给定字符串匹配的路径,而re2只记住当前的活动状态。这意味着re2会告诉您字符串是否与正则表达式匹配,但是它无法执行使用re对组所做的所有精美计算。结果,pyre2具有线性渐近时间复杂度(以不支持内置re的某些语法为代价),而re具有指数渐近复杂度。但这并不意味着对于基本的简单正则表达式,pyre2必须具有更好的性能。

请记住一件事:

您是从facebook仓库还是从python软件包索引下载pyre2的?如果您是从python软件包索引下载的,如果它无法处理给定的regex,它将回退到内置re库中(因此我认为那里可能会有一些小开销)-无论如何,如果您要与pyre2的正则表达式进行匹配不支持,它将回退,至少不会表现更好。

因此,很难看到正则表达式,这很难说,但是我的猜测是re2的运行速度变慢是由于以下原因之一:

  • 您与它们匹配的正则表达式和字符串非常简单(在这种情况下,使用re2没有任何优势)

  • 或者您是从pypi下载pyre2的,并且您正在捕获组并使用先行方式,而re2不支持,并且回退到了re)

由于您设法在C re2库中编译了相同的正则表达式,因此我想这是第一个原因,而不是第二个原因。