boost::xpressive解析决策树栈溢出问题求优化方案
Fixing Stack Overflow with boost::xpressive When Parsing Large Decision Trees
我完全理解你遇到的困境——递归式正则匹配对于深度极大的决策树来说,简直是栈空间的杀手。你用by_ref(tree)实现的递归匹配,每一层嵌套都会在调用栈上生成新的帧,当树的深度突破十万级时,默认的栈空间肯定撑不住。咱们一步一步来解决这个问题:
问题根源分析
你的tree规则是递归定义的:branch引用by_ref(tree),而tree又包含branch。boost::xpressive的递归匹配默认依赖程序的调用栈来管理嵌套状态,对于3000节点的小树没问题,但面对75000字符的大型树,栈深度直接拉满到13万帧,溢出是必然的。
具体解决方案
1. 弃用自动递归,改用手动递归+堆内存管理
把递归匹配的逻辑从正则表达式本身转移到代码层面,用堆上的栈结构(比如std::stack)模拟递归,彻底避开程序调用栈的限制。示例代码如下:
首先,提前编译好所有需要的正则表达式(避免重复编译浪费性能):
#include <boost/xpressive/xpressive.hpp> #include <stack> #include <string> using namespace boost::xpressive; // 提前编译所有正则,全局或作为类成员 sregex integral_number = sregex_compiler().compile("[-+]?[0-9]+"); sregex floating_point_number = sregex_compiler().compile("[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?"); sregex side = sregex_compiler().compile("def::BID|def::ASK"); sregex stdevs_from_mean_auction_time_gt = "StdevsFromMeanAuctionTimeGT(" >> floating_point_number >> ")"; sregex value_on_market_limit_ratio_gt = "ValueOnMarketLimitRatioGT<" >> side >> ">(" >> floating_point_number >> ")"; sregex value_on_market_delta_ratio_gt = "ValueOnMarketDeltaRatioGT(" >> floating_point_number >> ")"; sregex no_orders_on_opposite_side = sregex_compiler().compile("NoOrdersOnOppositeSide"); sregex is_pushing_price = sregex_compiler().compile("IsPushingPrice"); sregex is_desired = sregex_compiler().compile("IsDesired"); sregex predicate = value_on_market_limit_ratio_gt | value_on_market_delta_ratio_gt | stdevs_from_mean_auction_time_gt | no_orders_on_opposite_side | is_pushing_price | is_desired; sregex leaf = sregex_compiler().compile("SEARCH_TO_MAX|AMEND_TO_AVAILABLE|AMEND_TO_AVAILABLE_MINUS_RECENT_ORDER_SIZE|AMEND_TO_CURRENT_MINUS_RECENT_ORDER_SIZE|SEARCH_BY_RECENT_ORDER_SIZE|PULL|DO_NOTHING");
然后用迭代式的栈来解析树结构:
bool parse_tree(const std::string& s) { std::stack<size_t> parse_stack; size_t pos = 0; parse_stack.push(pos); while (!parse_stack.empty()) { pos = parse_stack.top(); parse_stack.pop(); // 先尝试匹配叶子节点 smatch leaf_match; if (regex_search(s.begin() + pos, s.end(), leaf_match, leaf, match_continuous)) { pos += leaf_match.length(); // 如果栈还有待解析的位置,更新后放回 if (!parse_stack.empty()) { parse_stack.top() = pos; } continue; } // 匹配分支节点 if (s.substr(pos, 7) != "Branch(") { return false; // 不匹配任何规则 } pos += 7; // 匹配predicate smatch pred_match; if (!regex_search(s.begin() + pos, s.end(), pred_match, predicate, match_continuous)) { return false; } pos += pred_match.length(); // 跳过第一个逗号 if (s[pos] != ',') return false; pos++; // 记录右子树的起始位置,先压栈,再处理左子树 size_t right_subtree_start = pos; parse_stack.push(right_subtree_start); // 处理左子树,把当前位置压栈继续解析 parse_stack.push(pos); } // 确保整个字符串都被解析完 return pos == s.size(); }
调用方式很简单:
std::string large_tree_str = "..."; // 你的大型决策树字符串 if (parse_tree(large_tree_str)) { // 解析成功 } else { // 解析失败 }
2. 临时应急:增大程序栈空间(不推荐长期使用)
如果只是临时解决问题,可以通过编译器选项增大栈空间。比如GCC下:
g++ -Wl,--stack,104857600 your_code.cpp -o your_program
这里设置了100MB的栈空间,能暂时缓解栈溢出,但本质上是治标不治本——如果树再大还是会出问题。
3. 优化正则表达式的匹配效率
- 确保所有正则表达式都是无歧义的,比如你的
side规则明确匹配def::BID或def::ASK,没有模糊的分支,避免xpressive进行不必要的回溯。 - 始终使用
match_continuous标志,确保匹配从当前位置开始,减少无效搜索。
总结
最可靠的方案是改用手动迭代+堆栈模拟递归,彻底摆脱程序调用栈的限制。这样不仅能解决栈溢出问题,还能让你对解析过程有更精细的控制,方便后续扩展或调试。
内容的提问来源于stack exchange,提问作者Michael
相关产品推荐
相关产品推荐

