最小化boost :: spirit编译时间

rav*_*int 27 c++ boost boost-spirit

任何减少boost :: spirit编译时间的想法?

我刚刚将flex解析器移植到boost :: spirit.EBNF有大约25条规则.

结果运行良好,运行时性能良好.

问题是编译需要永远!它需要大约十分钟,并且需要几乎一千兆字节的内存.原始的flex解析器在几秒钟内编译完成.

我正在使用boost版本1.44.0和Visual Studio 2008.


在Joel de Guzman的文章"最佳实践"中,它说

具有复杂定义的规则严重损害了编译器.我们已经看到超过一百行的规则,需要花费几分钟来编译

好吧,我没有接近那么久,但我的编译仍然需要超过几分钟

这是我语法中最复杂的部分.它是以某种方式分解成较小部分的候选者吗?

    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 ) ]
        ;
Run Code Online (Sandbox Code Playgroud)

通过评论部分语法,我发现了编译器花费最多时间的部分.

        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 )
                ]
            ;
Run Code Online (Sandbox Code Playgroud)

我不会称之为复杂,但它肯定是最长的规则.所以我想我会尝试拆分它,像这样:

    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 ) ]

        ;
Run Code Online (Sandbox Code Playgroud)

这节省了总编译时间几分钟!

奇怪的是,峰值内存要求保持不变,只需要更少的时间

所以,我感到更加充满希望,我在学习boost :: spirit方面的所有努力都是值得的.

我认为编译器需要以这种方式如此谨慎地引导有点奇怪.我原以为现代编译器会注意到这个规则只是一个独立的OR规则列表.


我已经花了七天的时间学习boost :: spirit并从flex移植一个小而真实的解析器.我的结论是它的工作原理和代码非常优雅.不幸的是,通过简单地扩展实际应用程序的教程示例代码来简单地使用,很快就会使编译器负担过重 - 内存和编译所花费的时间变得完全不切实际.显然有管理这个问题的技术,但它们需要我没有时间学习的神秘知识.我想我会坚持弯曲,这可能是丑陋和老式但相对简单和闪电般快.

hka*_*ser 17

我可以建议的一个技巧是分离两个构造函数的编译,你的词法分析器和你的语法.实现此目的的最简单方法是仅将这些构造函数的声明留在其相应的头文件中,并将这些函数的定义移动到单独的转换单元中.例如:

grammar.hpp:

template <typename Iterator>
struct grammar : qi::grammar<Iterator>
{
    grammar();   // declaration only
    // ...
};
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

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();
Run Code Online (Sandbox Code Playgroud)

对词法分析器对象做同样的事情.

这种方法比直接方法需要更多的工作,但它允许分配整个编译的内存和时间要求.这种方法的另一个优点是语法构造函数的任何更改都不需要重新编译除文件之外的任何内容grammar.cpp.

词法分析器的另一个建议:尝试尽可能减少token_def<>实例的使用.token_def<>只有在解析期间要将标记值作为属性访问时才需要使用.在所有其他情况下,您可能会逃避lex::stringlex::char_定义您的令牌.


rav*_*int 13

我必须得出结论:提升:精神,优雅,对于许多现实世界的解析问题来说不是一个可行的选择,因为即使专家也无法修复冗长的编译时间.

通常最好坚持像flex这样的东西,这可能是丑陋和老式的,但相对简单且闪电般快.

作为我认为这里的"现实世界"问题的一个例子,是一个解析器最重要部分的铁路图,它可以在几秒钟内进行灵活编译,但是提升:精神仍然在十分钟之后消失

在此输入图像描述

  • @ravenspoint在20年左右的时间之后,我遇到了这个答案,我冒昧地改变了你的措辞,这样它就不再是"绝对的"了.在结果形式中,我可以借给我+1.我喜欢Spirit,但我当然不会将它用于每一个解析工作 (4认同)
  • @ravenspoint我猜这是我的转折点:我以灵活的方式使用Spirit.我很少花太多时间开发解析器.不过,我曾经使用过CoCo/R和flex.这基本上是因为我将解析与处理代码混合在一起.你可以说我已经"适应"了我的工作流程,但我并不后悔.这也意味着如果你还没有(非常)经验,精神可能会非常令人沮丧:( (2认同)