任何减少boost :: spirit编译时间的想法?
我刚刚将flex解析器移植到boost :: spirit。 EBNF有大约25条规则。
结果运行良好,运行时性能良好。
问题是编译需要永远!它需要大约十分钟,并且需要几乎一千兆字节的内存。原始的flex解析器在几秒钟内编译完成。
我正在使用boost版本1.44.0和Visual Studio 2008。
在Joel de Guzman的文章'Best Practices'中,它说
具有复杂定义的规则严重损害了编译器。我们见过 超过一百的规则 线条很长,需要几个 分钟编译
好吧,我没有接近那么久,但我的编译仍然需要几分钟
这是我语法中最复杂的部分。它是以某种方式分解成较小部分的候选者吗?
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 ) ]
;
这比总编译时间节省了几分钟!!!
奇怪的是,峰值内存要求保持不变,只需要更短的时间
所以,我感到更有希望我所有努力学习提升::精神都值得。
我认为编译器需要以这种方式如此谨慎地引导有点奇怪。我原以为现代编译器会注意到这条规则只是一个独立的OR规则列表。
我花了7天的时间学习boost :: spirit并从flex移植一个小而真实的解析器。我的结论是它的工作原理和代码非常优雅。不幸的是,通过简单地扩展实际应用程序的教程示例代码来简单地使用,很快就会使编译器负担过重 - 内存和编译所花费的时间变得完全不切实际。显然有管理这个问题的技术,但它们需要我没有时间学习的神秘知识。我想我会坚持弯曲,可能是丑陋和老式但相对简单且闪电般快。
答案 0 :(得分:17)
我可以建议的一个技巧是分离两者的构造函数的编译,你的词法分析器和你的语法。实现此目的的最简单方法是仅将这些构造函数的声明留在其相应的头文件中,并将这些函数的定义移动到单独的转换单元中。例如:
grammar.hpp:
template <typename Iterator>
struct grammar : qi::grammar<Iterator>
{
grammar(); // declaration only
// ...
};
grammar_def.hpp:
// This file should not contain anything else.
#include "grammar.hpp"
// Definition of constructor.
template <typename Iterator>
grammar<Iterator>::grammar()
{
// initialize your rules here
}
grammar.cpp:
// This file should not contain anything else.
#include "grammar_def.hpp"
// Explicitly instantiate the constructor for the iterator type
// you use to invoke the grammar (here, as an example I use
// std::string::const_iterator).
typedef std::string::const_iterator iterator_type;
template grammar<iterator_type>::grammar();
为词法分析器对象做同样的事情。
这种方法比直接方法需要更多的工作,但它允许分配整个编译的内存和时间要求。这种方法的另一个优点是语法构造函数的任何更改都不需要重新编译除文件grammar.cpp
之外的任何内容。
词法分析器的另一个建议:尝试尽可能减少token_def<>
个实例的使用。仅当您希望在解析期间将标记值作为属性访问时,才需要使用token_def<>
。在所有其他情况下,您可能会使用lex::string
或lex::char_
来定义您的令牌。
答案 1 :(得分:13)
我必须得出结论:提升:精神,优雅,对于许多现实世界的解析问题来说不是一个可行的选择,因为即使专家也无法修复冗长的编译时间。
通常最好坚持像flex这样的东西,这可能是丑陋和老式的,但相对简单且闪电般快。
作为我认为“现实世界”问题的一个例子,这是一个解析器最重要部分的铁路图,灵活编译在几秒钟内完成,但是提升:精神仍然在十分钟之后消失