减少 boost::spirit 编译时间的任何想法?
我刚刚移植了一个 flex 解析器来 boost::spirit。EBNF 有大约 25 条规则。
结果运行良好,运行时性能良好。
问题是编译需要永远!这大约需要十分钟,并且需要近千兆字节的内存。最初的 flex 解析器在几秒钟内编译完毕。
我正在使用 boost 版本 1.44.0 和 Visual Studio 2008。
在 Joel de Guzman 的文章“最佳实践”中,它说
具有复杂定义的规则严重损害了编译器。我们已经看到了超过 100 行长并且需要几分钟才能编译的规则
好吧,我没有那么长的东西,但是我的编译仍然需要几分钟以上
这是我语法中最复杂的部分。它是否适合在某种程度上被分解成更小的部分?
rule
= ( tok.if_ >> condition >> tok.then_ >> *sequel ) [ bind( &cRuleKit::AddRule, &myRulekit ) ]
| ( tok.if_ >> condition >> tok.and_ >> condition >> tok.then_ >> *sequel ) [ bind( &cRuleKit::AddRule, &myRulekit ) ]
;
condition
= ( tok.identifier >> tok.oper_ >> tok.value ) [ bind( &cRuleKit::AddCondition, &myRulekit, _pass, _1, _2, _3 ) ]
| ( tok.identifier >> tok.between_ >> tok.value >> "," >> tok.value ) [ bind( &cRuleKit::AddConditionBetween, &myRulekit, _pass, _1, _3, _4 ) ]
;
sequel
= ( tok.priority_ >> tok.high_ ) [ bind( &cRuleKit::setPriority, &myRulekit, 3 ) ]
| ( tok.priority_ ) [ bind( &cRuleKit::setPriority, &myRulekit, 2 ) ]
| ( tok.interval_ >> tok.value ) [ bind( &cRuleKit::setInterval, &myRulekit, _2 ) ]
| ( tok.mp3_ >> tok.identifier ) [ bind( &cRuleKit::setMP3, &myRulekit, _2 ) ]
| ( tok.disable_ ) [ bind( &cRuleKit::setNextRuleEnable, &myRulekit, false ) ]
;
通过注释掉部分语法,我发现了编译器花费最多时间的部分。
set_reading
= tok.set_reading >> +attribute_reading
;
attribute_reading
= ( tok.name_ >> tok.identifier )
[
bind( &cPackage::Add, &myReadings, _pass, _2 )
]
| ( tok.nmea_ >> tok.identifier )
[
bind( &cPackage::setNextNMEA, &myReadings, _2 )
]
| ( tok.column_ >> tok.integer )
[
bind( &cPackage::setNextColumn, &myReadings, _2 )
]
| ( tok.precision_ >> tok.value )
[
bind( &cPackage::setNextPrecision, &myReadings, _2 )
]
| ( tok.unit_ >> tok.identifier )
[
bind( &cPackage::setNextUnit, &myReadings, _2 )
]
| ( tok.value_ >> tok.identifier )
[
bind( &cPackage::setNextValue, &myReadings, _2 )
]
| ( tok.qualifier_ >> tok.identifier >> tok.qual_col_ >> tok.integer )
[
bind( &cPackage::setNextQualifier, &myReadings, _2, _4 )
]
;
我不会称它为复杂,但它肯定是最长的规则。所以我想我会尝试将其拆分,如下所示:
set_reading
= tok.set_reading >> +attribute_reading
;
attribute_reading
= attribute_reading_name
| attribute_reading_nmea
| attribute_reading_col
| attribute_reading_precision
| attribute_reading_unit
| attribute_reading_value
| attribute_reading_qualifier
;
attribute_reading_name
= ( tok.name_ >> tok.identifier ) [ bind( &cPackage::Add, &myReadings, _pass, _2 ) ]
;
attribute_reading_nmea
= ( tok.nmea_ >> tok.identifier ) [ bind( &cPackage::setNextNMEA, &myReadings, _2 ) ]
;
attribute_reading_col
= ( tok.column_ >> tok.integer ) [ bind( &cPackage::setNextColumn, &myReadings, _2 ) ]
;
attribute_reading_precision
= ( tok.precision_ >> tok.value ) [ bind( &cPackage::setNextPrecision, &myReadings, _2 ) ]
;
attribute_reading_unit
= ( tok.unit_ >> tok.identifier ) [ bind( &cPackage::setNextUnit, &myReadings, _2 ) ]
;
attribute_reading_value
= ( tok.value_ >> tok.identifier ) [ bind( &cPackage::setNextValue, &myReadings, _2 ) ]
;
attribute_reading_qualifier
= ( tok.qualifier_ >> tok.identifier >> tok.qual_col_ >> tok.integer ) [ bind( &cPackage::setNextQualifier, &myReadings, _2, _4 ) ]
;
这从总编译时间中节省了几分钟!!!
奇怪的是,峰值内存需求保持不变,只是需要更少的时间
所以,我觉得我在学习 boost::spirit 方面的所有努力都将是值得的。
我确实认为编译器需要以这种方式进行如此仔细的引导有点奇怪。我原以为现代编译器会注意到该规则只是独立 OR 规则的列表。
我花了 7 天的大部分时间学习 boost::spirit 并从 flex 移植一个小而真实的解析器。我的结论是它可以工作并且代码非常优雅。不幸的是,简单地为实际应用程序扩展教程示例代码的天真使用很快就会使编译器负担过重——编译所花费的内存和时间变得完全不切实际。显然有一些技术可以解决这个问题,但它们需要我没有时间学习的神秘知识。我想我会坚持使用 flex ,这可能是丑陋和过时的,但相对简单且闪电般快速。