为何此处std::cmatch的性能比std::smatch慢约五倍?
问题背景
先生成一个长度为100000的随机字符串,代码如下:
const int length = 100000; std::uniform_int_distribution<int> distribution(0, 2); std::default_random_engine engine{1}; // 固定种子为1 // 仅用于测试,非最优实现 for(int i = 0; i < length; i++) // 生成随机的a/b/c字符 a.push_back('a' + distribution(engine)); std::regex r{ "abc" };
随后分别使用std::smatch和std::cmatch进行基准测试,测试代码如下:
std::smatch测试代码
std::smatch m; std::string a0 = a; int result = 0; // 防止编译器优化 while (std::regex_search(a0, m, r)) { a0 = m.suffix(); result += static_cast<int>(a0[0]); } return result;
std::cmatch测试代码
std::cmatch m; const char* currBegin = a.c_str(); int result = 0; while (std::regex_search(currBegin, m, r)) { // 为后续实际使用预留的代码 std::string_view v(m[0].first, m[0].second - m[0].first); currBegin = m.suffix().first; result += static_cast<int>(*currBegin); } return result;
测试环境为MSVC 19.29、Release模式、C++20标准,使用Catch2的BENCHMARK工具评估,结果显示std::cmatch的运行速度比std::smatch慢约5倍,以下是性能差异的核心原因:
核心原因分析
1. MSVC正则引擎对std::string的针对性优化
MSVC标准库的std::regex实现对std::string(对应std::smatch)路径做了深度优化:
- 引擎可以直接获取
std::string的长度信息,提前规划匹配逻辑,减少不必要的边界检查(无需每次扫描找\0终止符)。 - 针对
std::string的连续内存布局,引擎使用了更高效的内存访问模式,比如CPU缓存预取,提升了匹配效率。
而std::cmatch处理的是C风格const char*,引擎无法预知剩余字符串长度,每次匹配都需要从当前指针开始扫描,直到找到\0或匹配结果,这在长字符串的循环匹配中会累积大量额外开销。
2. 后缀处理的效率差异
在std::smatch的测试代码中,a0 = m.suffix()是将后缀部分构造为新的std::string——MSVC的std::string移动构造几乎无开销,且新字符串的长度信息是已知的,后续regex_search可以直接使用该长度,避免重复计算。
而std::cmatch中currBegin = m.suffix().first仅更新指针,每次regex_search都需要重新扫描剩余字符串的边界,这一操作在循环中被反复执行,进一步放大了性能差距。
3. 正则匹配的内部路径差异
MSVC的std::regex引擎对于std::string输入,会使用专门的匹配分支,减少了字符串类型转换和边界校验的开销;而C风格字符串的分支则需要处理更多的不确定性(比如未知的剩余长度),导致执行流程更复杂,运行效率更低。
4. 次要因素:额外代码的累积开销
std::cmatch测试代码中创建std::string_view的操作,虽然单次开销微小,但在高频循环中可能会有一定累积。不过即使移除这行代码,std::cmatch的性能依然会显著低于std::smatch,核心差异还是来自引擎对不同输入类型的处理策略。
内容的提问来源于stack exchange,提问作者o_oTurtle

