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

Posted

技术标签:

【中文标题】C++ 与 .NET 正则表达式性能【英文标题】:C++ vs .NET regex performance 【发布时间】:2013-11-05 20:47:39 【问题描述】:

在 Konrad Rudolph 对 a related question 的评论的提示下,我编写了以下程序来对 F# 中的正则表达式性能进行基准测试:

open System.Text.RegularExpressions
let str = System.IO.File.ReadAllText "C:\\Users\\Jon\\Documents\\pg10.txt"
let re = System.IO.File.ReadAllText "C:\\Users\\Jon\\Documents\\re.txt"
for _ in 1..3 do
  let timer = System.Diagnostics.Stopwatch.StartNew()
  let re = Regex(re, RegexOptions.Compiled)
  let res = Array.Parallel.init 4 (fun _ -> re.Split str |> Seq.sumBy (fun m -> m.Length))
  printfn "%A %fs" res timer.Elapsed.TotalSeconds

和 C++ 中的等价物:

#include "stdafx.h"

#include <windows.h>
#include <regex>
#include <vector>
#include <string>
#include <fstream>
#include <cstdio>
#include <codecvt>

using namespace std;

wstring load(wstring filename) 
    const locale empty_locale = locale::empty();
    typedef codecvt_utf8<wchar_t> converter_type;
    const converter_type* converter = new converter_type;
    const locale utf8_locale = locale(empty_locale, converter);
    wifstream in(filename);
    wstring contents;
    if (in)
    
        in.seekg(0, ios::end);
        contents.resize(in.tellg());
        in.seekg(0, ios::beg);
        in.read(&contents[0], contents.size());
        in.close();
    
    return(contents);


int count(const wstring &re, const wstring &s)
    static const wregex rsplit(re);
    auto rit = wsregex_token_iterator(s.begin(), s.end(), rsplit, -1);
    auto rend = wsregex_token_iterator();
    int count=0;
    for (auto it=rit; it!=rend; ++it)
        count += it->length();
    return count;


int _tmain(int argc, _TCHAR* argv[])

    wstring str = load(L"pg10.txt");
    wstring re = load(L"re.txt");

    __int64 freq, tStart, tStop;
    unsigned long TimeDiff;
    QueryPerformanceFrequency((LARGE_INTEGER *)&freq);
    QueryPerformanceCounter((LARGE_INTEGER *)&tStart);

    vector<int> res(4);

#pragma omp parallel num_threads(4)
    for(auto i=0; i<res.size(); ++i)
        res[i] = count(re, str);

    QueryPerformanceCounter((LARGE_INTEGER *)&tStop);
    TimeDiff = (unsigned long)(((tStop - tStart) * 1000000) / freq);
    printf("(%d, %d, %d, %d) %fs\n", res[0], res[1], res[2], res[3], TimeDiff/1e6);
    return 0;

两个程序都将两个文件加载为 unicode 字符串(我正在使用圣经的副本),构造一个重要的 unicode 正则表达式 \w?\w?\w?\w?\w?\w 并使用返回长度总和的正则表达式并行拆分字符串四次的拆分字符串(为了避免分配)。

在面向 64 位的发布版本中同时在 Visual Studio(为 C++ 启用 MP 和 OpenMP)中运行,C++ 需要 43.5 秒,F# 需要 3.28 秒(快 13 倍以上)。这并不让我感到惊讶,因为我相信 .NET JIT 将正则表达式编译为本机代码,而 C++ stdlib 解释它,但我想要一些同行评审。

我的 C++ 代码中是否存在性能错误,或者这是编译正则表达式与解释正则表达式的结果?

编辑:Billy ONeal 指出.NET 可以对\w 有不同的解释,所以我在一个新的正则表达式中明确表示:

[0-9A-Za-z_]?[0-9A-Za-z_]?[0-9A-Za-z_]?[0-9A-Za-z_]?[0-9A-Za-z_]?[0-9A-Za-z_]

这实际上使 .NET 代码的速度大大加快(C++ 也是如此),将 F# 的时间从 3.28 秒减少到 2.38 秒(快 17 倍以上)。

【问题讨论】:

如果您想要出色的正则表达式性能,请使用 Perl ;) 还应注意,您的 C++ 示例处理 Unicode 的方式与 .NET 示例不同。例如,\w 仅与 [0-9A-Za-z_] 匹配 std::regex(因为这就是 ECMAScript 所说的),而 .NET 示例参考语言环境信息来确定匹配的内容。 FWIW,这也不足为奇。除了对编译的担忧之外,C++ 中的正则表达式库简直就是糟糕透顶——我们经常在 C++ 聊天中对它大发雷霆。也就是说,为了使代码更具可比性,我将删除并行化……我并不期望有太大变化。 @Ben 公平地说,Boost.Xpressive 的(相关部分)仅限于编译时创建的正则表达式。这可能不是 Jon 想要比较的。 你的问题让我想学习函数式编程.. 【参考方案1】:

这些基准测试并没有真正的可比性——C++ 和 .NET 实现完全不同的正则表达式语言(ECMAScript 与 Perl),并且由完全不同的正则表达式引擎提供支持。 .NET(据我所知)受益于这里的GRETA project,它产生了一个绝对出色的正则表达式引擎,它已经调整了多年。相比之下,C++ std::regex 是最近添加的(至少在 MSVC++ 上,我假设您正在使用非标准类型 __int64 和朋友)。

您可以看到 GRETA 与更成熟的 std::regex 实现、boost::regex、here(尽管该测试是在 Visual Studio 2003 上完成的)相比如何。

您还应该记住,正则表达式的性能在很大程度上取决于您的源字符串和正则表达式。一些正则表达式引擎花费大量时间解析正则表达式以更快地通过更多源文本;只有在解析大量文本时才有意义的权衡。一些正则表达式引擎以扫描速度为代价换取匹配的成本相对较高(因此匹配的数量会产生影响)。这里有大量的权衡;一对输入真的会让故事变得模糊。

所以要更明确地回答您的问题:这种变化在正则表达式引擎中是正常的,无论它们是编译的还是解释的。看看上面 boost 的测试,最快和最慢的实现之间的差异通常相差数百倍——根据您的用例,17 倍并不是那么奇怪。

【讨论】:

ECMAScript 和 Perl 正则表达式如何解释这个特定的正则表达式? @Jon:看我对你的问题的评论——长话短说,\w 匹配不同的东西。 @Jon:另外,仅仅因为他们没有区别对待这个正则表达式并不意味着他们的设计没有权衡支持不同的功能。 您认为设计权衡是否可以解释此处 17 倍的性能差异,还是编译与解释? @Jon:我认为不同的引擎可以解释 17 倍的性能差异。 Boost 的数据比较了 5 种不同的解释正则表达式引擎(GRETA、boost::regexboost::regex with locales disabled、POSIX 和 PCRE)在许多情况下显示出数百倍的速度差异。

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

C++ 与正则表达式

一个实验性的C++编译期正则表达式parser

“1”的 C++ 字符串与“1”不匹配(尝试了正则表达式和布尔值 '==')

正则表达式无法与 C++ regex_match 一起按预期工作

C++ Primer 5th笔记(chap 17 标准库特殊设施)正则表达式

.Net 正则表达式匹配 $ 与字符串的结尾而不是行的结尾,即使启用了多行