[英]C++ backward regex search
我需要构建一个超高效的日志解析器(~1GB / s)。 我从英特尔实施了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
指向内存中加载的文件(2GB,因此无法复制)。 end
是与正则表达式匹配的已知偏移量。 但是当匹配的字符串远远不够时,效率非常低。 我本来期望“$”使得操作非常快,因为偏移量是作为结束位置给出的,但绝对不是。 如果end = 100000000
则操作需要~1s。
可以从Hyperscan开始匹配,但性能影响非常高(测试后大约每2分),因此这不是一个选项。
知道怎么做到这一点? 我正在使用C ++ 11(所以std实现了boost regex)。
最好的祝福
编辑:由于评论中出现了问题,我无法控制要使用的正则表达式。
我没有足够的声誉来评论XD。 我不认为以下是一个答案,它更像是另一种选择,但我必须回答,否则我不会联系到你。
我猜你不会找到一个技巧来使性能独立于位置(猜测它对于这种简单的正则表达式或其他什么是线性的)。
一个非常简单的解决方案是用例如posix regex.h(旧的但是金色;)或者强制正则表达式替换这个可怕的正则表达式lib。
这是一个例子:
#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
我下面提出的解决方案不起作用。 好吧,至少如果文中有多个“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倍的加速。 启动线程有一些开销
ps这是我的第一个C ++线程程序和第一个正则表达式,所以可能会有一些优化。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.