【问题标题】:regex performance java vs c++11正则表达式性能 java vs c++11
【发布时间】:2018-11-09 15:31:59
【问题描述】:

我正在学习 c++ 和 java 中的正则表达式。所以我用相同的表达式和相同的输入数对 c++11 正则表达式和 java 正则表达式进行了性能测试。奇怪的是,java regex 比 c++11 regex 快。我的代码有什么问题吗?请纠正我

Java 代码:

import java.util.regex.*;

public class Main {
    private final static int MAX = 1_000_000;
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        Pattern p = Pattern.compile("^[\\w._]+@\\w+\\.[a-zA-Z]+$");
        for (int i = 0; i < MAX; i++) {
            p.matcher("abcd_ed123.t12y@haha.com").matches();
        }
        long end = System.currentTimeMillis();
        System.out.print(end-start);
    }
}

C++ 代码:

#include <iostream>
#include <Windows.h>
#include <regex>

using namespace std;

int main()
{
    long long start = GetTickCount64();
    regex pat("^[\\w._]+@\\w+\\.[a-zA-Z]+$");
    for (long i = 0; i < 1000000; i++) {
        regex_match("abcd_ed123.t12y@haha.com", pat);
    }
    long long end = GetTickCount64();
    cout << end - start;
    return 0;
}

性能:

Java -> 1003ms
C++  -> 124360ms

【问题讨论】:

  • 显而易见的问题:你是如何编译 c++ 代码的?你有优化吗?
  • 所以你的结论是你的代码有问题,因为 Java 的性能比 C++ 好?
  • 请确认您已编译您的 C++ 代码并启用了优化。此外,您不仅在测量正则表达式,还在测量打印语句。我不会认为这是一个有效的测试。
  • 您是否为 Debug 或 Release 构建计时?它们之间可能存在显着的性能差异(Release 启用优化)。
  • 对!在发布版本中,我得到 VS2017 的 1672 毫秒。

标签: java c++ regex performance c++11


【解决方案1】:

使 C++ 示例可移植:

#include <iostream>
#include <chrono>
#include <regex>

using C = std::chrono::high_resolution_clock;
using namespace std::chrono_literals;

int main()
{
    auto start = C::now();
    std::regex pat("^[\\w._]+@\\w+\\.[a-zA-Z]+$");
    for (long i = 0; i < 1000000; i++) {
        regex_match("abcd_ed123.t12y@haha.com", pat);
    }
    std::cout << (C::now() - start)/1.0ms;
}

在 linux 上,使用clang++ -std=c++14 -march=native -O3 -o clang ./test.cpp 我得到595.970 ms。另请参阅 Live On Wandbox

java 在561 ms 中运行,在同一台机器上。

更新:Boost Regex 运行得更快,请参阅下面的比较基准

警告:像这样的综合基准测试很容易出错:编译器可能会感觉到没有观察到的副作用,并优化整个循环,只是举个例子。

更多乐趣:为混音增添动力

使用Boost 1.67Nonius Micro-Benchmarking Framework

我们可以看到 Boost 的 Regex 实现明显更快。

在线查看详细示例数据互动:https://plot.ly/~sehe/25/

Code Used

#include <iostream>
#include <regex>
#include <boost/regex.hpp>
#include <boost/xpressive/xpressive_static.hpp>
#define NONIUS_RUNNER
#include <nonius/benchmark.h++>
#include <nonius/main.h++>

template <typename Re>
void test(Re const& re) {
    regex_match("abcd_ed123.t12y@haha.com", re);
}

static const std::regex std_normal("^[\\w._]+@\\w+\\.[a-zA-Z]+$");
static const std::regex std_optimized("^[\\w._]+@\\w+\\.[a-zA-Z]+$", std::regex::ECMAScript | std::regex::optimize);
static const boost::regex boost_normal("^[\\w._]+@\\w+\\.[a-zA-Z]+$");
static const boost::regex boost_optimized("^[\\w._]+@\\w+\\.[a-zA-Z]+$", static_cast<boost::regex::flag_type>(boost::regex::ECMAScript | boost::regex::optimize));

static const auto boost_xpressive = []{
    using namespace boost::xpressive;
    return cregex { bos >> +(_w | '.' | '_') >> '@' >> +_w >> '.' >> +alpha >> eos };
}();

NONIUS_BENCHMARK("std_normal",      [] { test(std_normal);      })
NONIUS_BENCHMARK("std_optimized",   [] { test(std_optimized);   })
NONIUS_BENCHMARK("boost_normal",    [] { test(boost_normal);    })
NONIUS_BENCHMARK("boost_optimized", [] { test(boost_optimized); })
NONIUS_BENCHMARK("boost_xpressive", [] { test(boost_xpressive); })

注意这是 Hotspot JVM JIT 编译器的输出:

这是使用

生成的

LD_PRELOAD=/home/sehe/Projects/stackoverflow/fcml-1.1.3/example/hsdis/.libs/libhsdis-amd64.so ./jre1.8.0_171/bin/java -XX:+UnlockDiagnosticVMOptions -XX: +PrintAssembly Main 2>&1 > disasm.a

【讨论】:

  • 尝试将std::regex::optimize 传递给构造函数以强制创建 FSA(理论上)
  • @Mgetz 优化使引擎生成 DFA(确定性有限自动机)而不是 NFA(非确定性有限自动机)。它们都是有限状态自动机,但生成 DFA 需要付出更多努力(尽管它会运行得更快并且不能代表所有正则表达式)。
  • @Mgetz 或 optimize 做了一些超出我们预期的事情。添加clang+libc++。另外,为了好玩:使用 GCC 的比较基准;libstdc++;Boost1.67:plot.ly/~sehe/21 (benchmark code)
  • @rustyx 这是一个很好的电话。添加ECMAScript | optimized 使优化的正则表达式稍微快一点——更有意义。 Updated the answer(再次)。
  • 速度也取决于实现,在我对 Linux 上 arm64 clang 的测试中,在发布模式下进行了全面优化,我发现libc++ 正则表达式实现比libstdc++ 快约 16 倍。并且比 boost 版本快大约 2 倍。
【解决方案2】:

只是添加到已经提供的答案...

是的,C++11 std::regex 比 Java 稍慢(即使在发布模式下)。

但是使用 JIT 的 PCRE2 快 3 倍:

#include <iostream>
#include <chrono>
#define PCRE2_STATIC
#define PCRE2_CODE_UNIT_WIDTH 8
#include "pcre2.h"

using namespace std;
using namespace std::chrono;
using namespace std::chrono_literals;

int main()
{
    auto start = high_resolution_clock::now();
    int errn;
    PCRE2_SIZE erroffset;
    auto pattern = (PCRE2_SPTR8)"^[\\w._]+@\\w+\\.[a-zA-Z]+$";
    pcre2_code* re = pcre2_compile(pattern, PCRE2_ZERO_TERMINATED, 0, &errn, &erroffset, nullptr);
    if (!re)
        cerr << "pcre2_compile failed\n";
    pcre2_match_data* match_data = pcre2_match_data_create_from_pattern(re, nullptr);
    for (long i = 0; i < 1000000; i++) {
        auto text = (PCRE2_SPTR8)"abcd_ed123.t12y@haha.com";
        int rc = pcre2_match(re, text, PCRE2_ZERO_TERMINATED, 0, 0, match_data, nullptr);
        if (rc <= 0)
            cerr << "pcre2_match failed\n";
    }
    auto end = high_resolution_clock::now();
    cout << (end - start) / 1ms << "\n";
    return 0;
}

结果:

  • PCRE2 v10.21:139ms
  • Java:440ms

【讨论】:

  • "JIT" 暗示 PCRE 是否直接编译成机器指令?从我所见,正则表达式通常编译成 DFA,引擎经过高度优化,但显然是提前编译的代码。为数据点 +1
  • @sehe 是的,它将directly 编译成机器指令。有点难以击败
  • TIL。很棒的图书馆。不错的补充!
【解决方案3】:

正如许多评论者所指出的,听起来您正在以debug 模式编译您的 C++ 代码,这会关闭许多编译器优化并为您的程序添加一些额外的诊断代码。

由于您使用的是 Visual Studio 2017,请查找解决方案配置下拉菜单并将其从 Debug 更改为 Release

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-09-17
    • 1970-01-01
    • 1970-01-01
    • 2011-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-18
    相关资源
    最近更新 更多