C ++向后正则表达式搜索

时间:2018-06-23 09:26:10

标签: c++ regex c++11

我需要构建一个超高效的日志解析器(〜1GB / s)。我实现了Intel的Hyperscan库(https://www.hyperscan.io),它可以很好地用于:

  • 计算指定事件的发生次数
  • 给出比赛的结束位置

限制之一是无法报告捕获组,只能报告结束偏移量。对于大多数比赛,我只使用计数,但对于其中的10%,必须对比赛进行解析以计算进一步的统计信息。

面临的挑战是有效地运行正则表达式以获取Hyperscan匹配,而仅知道结束偏移量。目前,我尝试过:

string data(const string * block) const {
   std::regex nlexpr("\n(.*)\n$");
   std::smatch match;
   std::regex_search((*block).begin(), (*block).begin() + end, match, nlexpr);
   return match[1];
}
  • block指向加载到内存中的文件(2 GB,因此无法复制)。
  • end是与正则表达式匹配的已知偏移量。

但是当要匹配的字符串在块中很远时,效率极低。我希望“ $”可以使操作很快,因为偏移量是作为结束位置给出的,但绝对不是。如果end = 100000000,该操作大约需要1s。

可以从Hyperscan开始比赛,但是对性能的影响非常大(测试后大约每2分之一),所以这不是一个选择。

任何想法如何实现这一目标?我正在使用C ++ 11(因此std实现了boost regex)。

最诚挚的问候

编辑: 当问题出现在评论中时,我对要使用的正则表达式没有任何控制。

2 个答案:

答案 0 :(得分:2)

我的信誉不足,无法评论XD。我不认为以下是答案,它是替代方法,尽管如此,我必须做出回答,否则我将无法找到您。

我想您不会找到使性能独立于位置的窍门(猜测这种简单正则表达式或其他东西的线性关系)。

一个非常简单的解决方案是用例如posix regex.h(旧但用金;)或增强regex。

这里是一个例子:

#include <iostream>
#include <regex>
#include <regex.h>
#include <chrono>
#include <boost/regex.hpp>
inline auto now = std::chrono::steady_clock::now;
inline auto toMs = [](auto &&x){
    return std::chrono::duration_cast<std::chrono::milliseconds>(x).count();
};

void cregex(std::string const&s, std::string const&p)
{
    auto start = now();
    regex_t r;
    regcomp(&r,p.data(),REG_EXTENDED);
    std::vector<regmatch_t> m(r.re_nsub+1);
    regexec(&r,s.data(),m.size(),m.data(),0);
    regfree(&r);
    std::cout << toMs(now()-start) << "ms " << std::string{s.cbegin()+m[1].rm_so,s.cbegin()+m[1].rm_eo} << std::endl;
}

void cxxregex(std::string const&s, std::string const&p)
{
    using namespace std;
    auto start = now();
    regex r(p.data(),regex::extended);
    smatch m;
    regex_search(s.begin(),s.end(),m,r);
    std::cout << toMs(now()-start) << "ms " << m[1] << std::endl;
}
void boostregex(std::string const&s, std::string const&p)
{
    using namespace boost;
    auto start = now();
    regex r(p.data(),regex::extended);
    smatch m;
    regex_search(s.begin(),s.end(),m,r);
    std::cout << toMs(now()-start) << "ms " << m[1] << std::endl;
}

int main()
{
    std::string s(100000000,'x');
    std::string s1 = "yolo" + s;
    std::string s2 = s + "yolo";
    std::cout << "yolo + ... -> cregex "; cregex(s1,"^(yolo)");
    std::cout << "yolo + ... -> cxxregex "; cxxregex(s1,"^(yolo)");
    std::cout << "yolo + ... -> boostregex "; boostregex(s1,"^(yolo)");
    std::cout << "... + yolo -> cregex "; cregex(s2,"(yolo)$");
    std::cout << "... + yolo -> cxxregex "; cxxregex(s2,"(yolo)$");
    std::cout << "... + yolo -> boostregex "; boostregex(s2,"(yolo)$");
}

礼物:

yolo + ... -> cregex 5ms yolo
yolo + ... -> cxxregex 0ms yolo
yolo + ... -> boostregex 0ms yolo
... + yolo -> cregex 69ms yolo
... + yolo -> cxxregex 2594ms yolo
... + yolo -> boostregex 62ms yolo

答案 1 :(得分:0)

我刚刚意识到...

我下面提出的解决方案不起作用。好吧,至少在文本中有多个“ yolo”。它不返回“在字符串中找到的第一个实例”,但返回“在字符串的子字符串中找到的第一个实例”。因此,如果您有4个CPU,则该字符串将分为4个子字符串。第一个返回“ yolo”“胜利”的人。如果您只想查看“ yolo”是否在文本中的任何位置,但是如果您想获取第一个实例的位置,则可能没有问题。

旧答案

基于OZ的答案,我编写了一个并行版本。编辑:现在使用信号量尽早完成。

#include <mutex>
#include <condition_variable>
std::mutex g_mtx;
std::condition_variable g_cv;
int g_found_at = -1;

void thread(
    int id,
    std::string::const_iterator begin,
    std::string::const_iterator end,
    const boost::regex& r,
    boost::smatch* const m)
{
    boost::smatch m_i;
    if (regex_search(begin, end, m_i, r))
    {
        *m = m_i;
        std::unique_lock<std::mutex> lk(g_mtx);
        g_found_at = id;
        lk.unlock();
        g_cv.notify_one();
    }
}
#include <thread>
#include <vector>
#include <memory>
#include <algorithm>
#include <chrono>
using namespace std::chrono_literals;
void boostparregex(std::string const &s, std::string const &p)
{
    {
        std::unique_lock<std::mutex> lk(g_mtx);
        g_found_at = -1;
    }
    auto nrOfCpus = std::thread::hardware_concurrency() / 2;
    std::cout << "(Nr of CPUs: " << nrOfCpus << ") ";
    auto start = steady_clock::now();
    boost::regex r(p.data(), boost::regex::extended);
    std::vector<std::shared_ptr<boost::smatch>> m; m.reserve(nrOfCpus);
    std::generate_n(std::back_inserter(m), nrOfCpus, []() { return std::make_shared<boost::smatch>(); });
    std::vector<std::thread> t; t.reserve(nrOfCpus);
    auto sizePerThread = s.length() / nrOfCpus;
    for (size_t tId = 0; tId < nrOfCpus; tId++) {
        auto begin = s.begin() + (tId * sizePerThread);
        auto end = tId == nrOfCpus - 1 ? s.end() : s.begin() + ((tId + 1) * sizePerThread) - 1;
        t.push_back(std::thread(thread, (int)tId, begin, end, r, m[tId].get()));
    }
    {
        std::unique_lock<std::mutex> lk(g_mtx);
        g_cv.wait_for(lk, 10s, []() { return g_found_at >= 0; });
    }
    {
        std::unique_lock<std::mutex> lk(g_mtx);
        if (g_found_at < 0) std::cout << "Not found! "; else std::cout << m[g_found_at]->str() << " ";
    }
    std::cout << toMs(steady_clock::now() - start) << "ms " << std::endl;
    for (auto& thr : t) thr.join();
}

哪个给我这个输出(vs2017下没有posix)

yolo + ... -> cxxregex 0ms yolo
yolo + ... -> boostregex 1ms yolo
yolo + ... -> boostparregex (Nr of CPUs: 4) yolo 13ms
... + yolo -> cxxregex 5014ms yolo
... + yolo -> boostregex 837ms yolo
... + yolo -> boostparregex (Nr of CPUs: 4) yolo 222ms

我在4个CPU上的速度提高了4倍。启动线程有一些开销

p.s。这是我的第一个C ++线程程序和第一个regex,所以可能会有一些优化。