我需要构建一个超高效的日志解析器(〜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)。
最诚挚的问候
编辑: 当问题出现在评论中时,我对要使用的正则表达式没有任何控制。
答案 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,所以可能会有一些优化。