正则表达式.h 性能

Posted

技术标签:

【中文标题】正则表达式.h 性能【英文标题】:regex.h performance 【发布时间】:2015-06-26 07:50:47 【问题描述】:

我试图通过正则表达式匹配找出 Python、Cython 和纯 C 之间违反直觉的性能差异。

有一个小示例程序,它获取一个源文本文件 (17 KB),一个包含 2000 个单词的字典,用这些单词 (word1|word2|...) 创建一个正则表达式,并在源文件。

首先,我完成了一个纯 Python 实现,如下所示:

def scanFile(filename, patterns):
   pattern_regex = re.compile('|'.join(patterns))
   pageContent = open(filename).read()
   matchingPatterns = set()
   for matchObj in pattern_regex.finditer(pageContent):
      matchingPatterns.add(matchObj.group(0))

   return matchingPatterns

然后,我尝试通过在 regex.h 而不是 Python 的 re 模块之上重新实现 Cython 来优化它。

cdef extern from "regex.h" nogil:
   ctypedef struct regmatch_t:
      int rm_so
      int rm_eo
   ctypedef struct regex_t:
      pass
   int REG_EXTENDED
   int regcomp(regex_t* preg, const char* regex, int cflags)
   int regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags)
   void regfree(regex_t* preg) 

def matchPatterns(bytes pageContent, bytes regex):
   cdef set matchingPatterns = set()
   cdef regex_t regex_obj
   cdef regmatch_t regmatch_obj[1]
   cdef int regex_res = 0
   cdef int current_str_pos = 0

   regcomp(&regex_obj, regex, REG_EXTENDED)
   regex_res = regexec(&regex_obj, pageContent[current_str_pos:], 1, regmatch_obj, 0)
   while regex_res == 0:
      matchingPatterns.add(pageContent[current_str_pos + regmatch_obj[0].rm_so: current_str_pos + regmatch_obj[0].rm_eo])
      current_str_pos += regmatch_obj[0].rm_eo
      regex_res = regexec(&regex_obj, pageContent[current_str_pos:], 1, regmatch_obj, 0)

   regfree(&regex_obj)
   return matchingPatterns

然而,性能恰恰相反:Cython+regex.h 大约需要 2.34 秒,而 Python 需要 0.92 秒。

在运行了一些分析和自定义注释掉的代码后,我确认怀疑它归结为 regexec,每次调用需要 10 毫秒。

只是为了确保不是 Cython 有问题,准备了一个独立的 C 单元测试,它使用相同的输入和 regex.h,它还显示出比 Python 更差的结果(大约 1.60 秒,即比 Python 慢 60%) .

因此,综上所述,我将感谢您了解 regexec 为何表现如此糟糕的原因。

我在 Python 2.7.10、gcc 4.9.2、Cython 0.22 上运行它,平台是 Cygwin/Windows。在 Ubuntu 上运行相同的程序时,我也有类似的差异。

【问题讨论】:

你有独立 C 单元测试的代码吗? 为什么你会认为 C 的正则表达式引擎比各种 Python 实现中使用的引擎更快?它们都可能作为本机 C 代码运行,并且在实现正则表达式引擎时可能存在许多变化。 您可能需要更广泛的 RE 才能对性能做出结论。您可能发现了一个 RE 引擎的弱点,但仅仅因为您只是在尝试 OR 就错过了它的优势。可能涉及其他代码,例如你在 Python 中使用 set(),你在 C 中也这样做了吗? BRE/ERE 的一种可能实现是在回溯引擎之上实现它——而不是最后的接受节点,它跟踪最长的匹配并让引擎继续尝试直到耗尽所有的可能性。结果,这样的实现最终比回溯引擎做更多的工作。至于这样做的原因——由于BRE支持反向引用,所以无论如何都需要实现一个回溯引擎。他们可能不会费心为没有反向引用的模式包含一个有效的实现。 @unwind - 主要是因为 python re 的性能远不如 boost::regex。因为我想试用 Cython(包括学习经验和优化),所以我选择了 regex.h,因为它是纯 C。我意识到在 Cython 中包含 C++ 是可能的,并且 boost::regex 和 regex.h 并不是天生平等的,但我希望两者都能获得相似的收益。差异引发了这个问题。 @cdarke - 事实上,很可能 Python 可以更好地应对大量 OR 模式。虽然从 regex.h 文档中并不清楚它确实没有很好地应对它。 【参考方案1】:

根据问题中的内容,我可以假设几个问题: - 您在 Windows 和 Cygwin 上使用 POSIX - 这是开销,Windows 不是 POSIX 系统。 -pcre(让我假设pcre2)和regex.h之间有比较 - 独立编译的代码与导出的函数不同(编译器不能假设任何东西) - 独立的 C 程序有很大的足迹告诉你模式的重新编译或其他一些东西正在引擎盖下发生。 - 编译选项和潜在的别名总是很难比较。

除了独立程序的源代码外,总是使用翻译器/转译器可能会产生滞后。 优化现在的任务是让你的编译器清楚地知道你在做什么并让它工作。

对不起,这部分与问题本身无关,但看起来你不需要 RE,但需要基本的字符串匹配算法或一些简洁的结构,如前缀树和简单循环来完成你的任务。

【讨论】:

以上是关于正则表达式.h 性能的主要内容,如果未能解决你的问题,请参考以下文章

C# |正则表达式 |如何提高我的正则表达式性能

正则表达式性能下降

mongodb 正则表达式查询性能问题

C++ 与 .NET 正则表达式性能

C#正则表达式性能纯相对JS

MongoDB,通过正则表达式对索引字段的查询性能