2016-05-14 5 views
7

Ich versuche mit dem neuen Spirit X3 (Boost 1.61.0) in den Griff zu bekommen.Kompiliere mal mit Boost Spirit x3

Meine Maschine ist ein MacBook Pro (i7-4750HQ) unter Linux.

Nachdem ich die Version 2 von Spirit verwendet habe, war ich an große Kompilierzeiten gewöhnt, aber das fühlt sich nicht richtig an. Für die folgenden ersten Schritte eines Ausdrucksparsers benötigt die Kompilation 20s.

Ich dachte X3 wird schneller sein, also ist das vernünftig? Ist mein Code suboptimal?

Compiler-Einstellungen (Klirren 3.8.0)

clang++ -c -pipe -std=c++14 -ftemplate-depth=512 -g -w -Wall -Wno-unused-parameter -fPIC 

Code:

//#define BOOST_SPIRIT_X3_DEBUG 
#include <iostream> 

#include <boost/spirit/home/x3.hpp> 
#include <boost/spirit/home/x3/support/ast/variant.hpp> 
#include <boost/fusion/include/adapt_struct.hpp> 

#include <string> 
#include <vector> 

//-------------------------------------------------------------------------------------------------- 
namespace client { namespace ast 
{ 
    namespace fusion = boost::fusion; 
    namespace x3 = boost::spirit::x3; 

    struct number : x3::variant<int, double> { 
     using base_type::base_type; 
     using base_type::operator=; 
    }; 

    struct add_ast; 
    struct mult_ast; 
    struct block_ast; 
    struct function; 

    struct expr_ast : x3::variant< 
      number, 
      x3::forward_ast<function>, 
      x3::forward_ast<add_ast>, 
      x3::forward_ast<mult_ast>, 
      x3::forward_ast<block_ast> 
     > { 
     using base_type::base_type; 
     using base_type::operator=; 
    }; 

    struct add_ast { 
     expr_ast lhs; 
     bool  add; 
     expr_ast rhs; 
    }; 

    struct mult_ast { 
     expr_ast lhs; 
     bool  mult; 
     expr_ast rhs; 
    }; 

    struct block_ast { 
     expr_ast body; 
    }; 

    struct function { 
     std::string   name; 
     std::vector<expr_ast> params; 
    }; 
}} 

//-------------------------------------------------------------------------------------------------- 
BOOST_FUSION_ADAPT_STRUCT(client::ast::add_ast, 
    (client::ast::expr_ast, lhs), 
    (bool, add), 
    (client::ast::expr_ast, rhs) 
) 
BOOST_FUSION_ADAPT_STRUCT(client::ast::mult_ast, 
    (client::ast::expr_ast, lhs), 
    (bool, mult), 
    (client::ast::expr_ast, rhs) 
) 
BOOST_FUSION_ADAPT_STRUCT(client::ast::block_ast, 
    (client::ast::expr_ast, body) 
) 
BOOST_FUSION_ADAPT_STRUCT(client::ast::function, 
    (std::string, name), 
    (std::vector<client::ast::expr_ast>, params) 
) 

//-------------------------------------------------------------------------------------------------- 
namespace client { namespace parser 
{ 
    namespace x3 = boost::spirit::x3; 

    const x3::rule<class expr,  ast::expr_ast> expr  = "expr"; 
    const x3::rule<class add_expr, ast::expr_ast> add_expr = "add_expr"; 
    const x3::rule<class mult_expr, ast::expr_ast> mult_expr = "mult_expr"; 
    const x3::rule<class block_expr, ast::expr_ast> block_expr = "block_expr"; 

    auto const number = x3::rule<class number, ast::number> {"number"} 
         = (x3::int_ >> !x3::lit('.')) | x3::double_; 

    auto const fct_name = x3::rule<class fct_name, std::string> {"fct_name"} 
         = x3::lexeme[ *x3::alpha >> *(x3::alnum | x3::char_('_')) ]; 

    auto const function = x3::rule<class function, ast::function> {"function"} 
         = fct_name >> x3::lit("(") >> -expr % ',' >> ")"; 

    auto const simple_expr = x3::rule<class simple_expr, ast::expr_ast> {"simple_expr"} 
          = function | number; 

    auto const block_term = x3::rule<class block_term, ast::block_ast> {"block_term"} 
          = "(" >> expr >> ")"; 

    auto const mult_term = x3::rule<class mult_term, ast::mult_ast> {"mult_term"} 
         = block_expr 
          >> ((x3::lit("*") >> x3::attr(true)) | (x3::lit("/") >> x3::attr(false))) 
          >> mult_expr; 

    auto const add_term = x3::rule<class add_term, ast::add_ast> {"add_term"} 
         = mult_expr 
          >> ((x3::lit("+") >> x3::attr(true)) | (x3::lit("-") >> x3::attr(false))) 
          >> add_expr; 

    auto const block_expr_def = block_term | simple_expr; 
    auto const mult_expr_def = mult_term | block_expr; 
    auto const add_expr_def = add_term | mult_expr; 
    auto const expr_def  = add_expr; 

    BOOST_SPIRIT_DEFINE(expr, add_expr, mult_expr, block_expr); 
}} 

//-------------------------------------------------------------------------------------------------- 
namespace client { namespace ast 
{ 
    struct printer 
    { 
     typedef std::string result_type; 

     std::string operator()(const expr_ast &ast) const 
     { 
      return boost::apply_visitor(printer(), ast); 
     } 
     std::string operator()(const number &value) const 
     { 
      return boost::apply_visitor(printer(), value); 
     } 

     std::string operator()(const add_ast &expr) const { 
      return "(" + boost::apply_visitor(printer(), expr.lhs) + (expr.add?" + ":" - ") 
        + boost::apply_visitor(printer(), expr.rhs) + ")"; 
     } 

     std::string operator()(const mult_ast &expr) const { 
      return "(" + boost::apply_visitor(printer(), expr.lhs) + (expr.mult?" * ":"/") 
        + boost::apply_visitor(printer(), expr.rhs) + ")"; 
     } 

     std::string operator()(const block_ast &expr) const { 
      return boost::apply_visitor(printer(), expr.body); 
     } 

     std::string operator()(const function &fct) const 
     { 
      std::string result = fct.name + "("; 
      for (std::size_t i = 0; i < fct.params.size(); ++i) { 
       result += printer()(fct.params[i]); 
       if (i != fct.params.size() - 1) 
        result += ","; 
      } 
      result += ")"; 
      return result; 
     } 

     std::string operator()(int const& value) const 
     { 
      return std::to_string(value); 
     } 
     std::string operator()(double const& value) const 
     { 
      return std::to_string(value); 
     } 
    }; 
}} 

//-------------------------------------------------------------------------------------------------- 
int main() 
{ 
    std::vector<std::string> storage = { 
     "foo()", "-foo()", 
     "f1_2()", 
     "foo_bar()", 
     "foo(bar (42, baz()))", 
     "foo(5)", "foo(-5)", 
     "foo(1.1, foo(4.21e-2, 4., 6))", 
     "1.1", "-1.1", 
     "1 * 1", 
     "foo(1 * 1) * bar(42)", 
     "foo(2 + 5.5, bar()*3.4-7)", 
     "foo(2 + 5.5, bar(baz(-5/foo())) * 3.4 - 7)", 
     "4 + 5 * 6", 
     "1+2+3+4*5*6*-7+-8*+9-0", 
     "(foo())", 
     "foo() * ((1+2)+3*(2+3))", 
     "(1+2)*3", "1+2*3", 
     "foo" 
    }; 

    using boost::spirit::x3::ascii::space; 

    for (const auto &item : storage) { 
     using client::parser::expr; // Our grammar 
     client::ast::expr_ast ast; // Our tree 

     std::string::const_iterator iter = item.begin(); 
     std::string::const_iterator end = item.end(); 
     bool r = phrase_parse(iter, end, expr, space, ast); 

     if (r && iter == end) 
     { 
      std::cout << "Ok: " << item << " result: " << client::ast::printer()(ast) << std::endl; 
     } 
     else 
     { 
      std::cout << "Fail: " << item << std::endl; 
     } 
    } 
} 
+0

15s für mich auf einem modernen MacBook Pro mit -O0, 27s mit -O3. Musste 2 # includes hinzufügen, um es kompilieren zu lassen - stdexcept und exception. –

+0

^^ das war mit Apfel klingeln. –

+0

Obwohl Joel die Maschine nicht spezifiziert, [gibt er das Timing] (http://boost-spirit.com/home/2013/02/23/spirit-x3-on-github/) für die Kompilierung des Beispiels calc4 als ~ 5s. Ihr Beispiel scheint komplexer zu sein, also scheinen die Zeiten, die Sie sehen, nicht unangemessen.In Anbetracht dessen, was Sie für die Menge an Code bekommen, die Sie schreiben mussten (und die Menge an Arbeit, die der Compiler zu tun hat) ... –

Antwort

2

Während dies tatsächlich eine Regression in Spi sein könnte rit X3 wie @sehe legt nahe, gibt es eine Abhilfe mit der aktuellen Version:

ändern Sie alle Regeln an dem Ausdruck Rekursion wie dies unter:

const x3::rule<class block_term, ast::block_ast> block_term = "block_term"; 
auto const block_term_def = x3::rule<class block_term, ast::block_ast> {"block_term"} 
         = "(" >> expr >> ")"; 

BOOST_SPIRIT_DEFINE(block_term) 

Dies reduziert die Zeit drastisch kompilieren und der Parser gut funktioniert. Parser-Performance scheint die gleiche zu sein (sehr unwissenschaftliche Tests!).

+1

Das funktioniert, weil es die rekursive Instanziierung bricht. Es hat sich jedoch für mich nicht als zuverlässig erwiesen. Es scheint, dass Compiler über aggressives Inlining zu schlau geworden sind. Dies ist in der Tat der allgemeine Kern eines Workarounds (die zuverlässigere Sache ist "any_parser", aber das verliert Generizität und ist viel weniger bequem). Immer noch ein QOI-Thema, denke ich. – sehe

4

Dies sieht aus wie eine schwere Regression zu mir.

Es dauerte sehr lange auf meiner Maschine:

  • gcc 5: langsam immer mehr Speicher nach 4min30s zu 3GiB mit bis, vom Assembler Stufe ~ 20s gefolgt:

    g++-5 -std=c++14 -Wall -pedantic -Wextra -fsanitize=undefined,address -Wno-unused -g -O3 -isystem /home/sehe/custom/nonius/include -isystem /home/sehe/custom/boost_1_60_0 -pthread -march=native test.cpp -c -o test.o 
    test.cpp:119:62: warning: extra ‘;’ [-Wpedantic] 
        BOOST_SPIRIT_DEFINE(expr, add_expr, mult_expr, block_expr); 
                      ^
    g++-5 -std=c++14 -Wall -pedantic -Wextra -fsanitize=undefined,address -Wno-unused -g -O3 -isystem /home/sehe/custom/nonius/include -isystem /home/sehe/custom/boost_1_60_0 -pthread -march=native test.o -o test -L /home/sehe/custom/boost_1_60_0/stage/lib/ -Wl,-rpath,/home/sehe/custom/boost_1_60_0/stage/lib -lboost_system -lboost_regex -lboost_thread -lboost_iostreams -lboost_serialization -lboost_filesystem -lboost_chrono -lrt -lboost_unit_test_framework -lpugixml -lssl -lcrypto -lxml2 
    
    real 4m50.427s 
    user 4m48.248s 
    sys 0m1.856s 
    
  • Klirren 3.6: nicht mit der Tiefe Vorlage Instanziierung überschritten

    /home/sehe/custom/boost_1_60_0/boost/spirit/home/x3/support/context.hpp|30 col 25| fatal error: recursive template instantiation exceeded maximum depth of 256 
    

Dies gibt dann einen direkten Hinweis darauf, was es verursacht.

Meine erste Vermutung war, dass x3::variant an den Compiler führen könnte aggressiver Inline Dinge, aber replacing with boost::variant half nicht viel:

g++-5 -std=c++14 -Wall -pedantic -Wextra -fsanitize=undefined,address -Wno-unused -g -O3 -isystem /home/sehe/custom/nonius/include -isystem /home/sehe/custom/boost_1_60_0 -pthread -march=native test.cpp -c -o test.o 
test.cpp:135:62: warning: extra ‘;’ [-Wpedantic] 
    BOOST_SPIRIT_DEFINE(expr, add_expr, mult_expr, block_expr); 
                  ^
g++-5 -std=c++14 -Wall -pedantic -Wextra -fsanitize=undefined,address -Wno-unused -g -O3 -isystem /home/sehe/custom/nonius/include -isystem /home/sehe/custom/boost_1_60_0 -pthread -march=native test.o -o test -L /home/sehe/custom/boost_1_60_0/stage/lib/ -Wl,-rpath,/home/sehe/custom/boost_1_60_0/stage/lib -lboost_system -lboost_regex -lboost_thread -lboost_iostreams -lboost_serialization -lboost_filesystem -lboost_chrono -lrt -lboost_unit_test_framework -lpugixml -lssl -lcrypto -lxml2 

real 3m55.728s 

Ohne Unterschied in resuts:

Ok: foo() result: foo() 
Fail: -foo() 
Ok: f1_2() result: f1_2() 
Ok: foo_bar() result: foo_bar() 
Ok: foo(bar (42, baz())) result: foo(bar(42,baz())) 
Ok: foo(5) result: foo(5) 
Ok: foo(-5) result: foo(-5) 
Ok: foo(1.1, foo(4.21e-2, 4., 6)) result: foo(1.100000,foo(0.042100,4.000000,6)) 
Ok: 1.1 result: 1.100000 
Ok: -1.1 result: -1.100000 
Ok: 1 * 1 result: (1 * 1) 
Ok: foo(1 * 1) * bar(42) result: (foo((1 * 1)) * bar(42)) 
Ok: foo(2 + 5.5, bar()*3.4-7) result: foo((2 + 5.500000),((bar() * 3.400000) - 7)) 
Ok: foo(2 + 5.5, bar(baz(-5/foo())) * 3.4 - 7) result: foo((2 + 5.500000),((bar(baz((-5/foo()))) * 3.400000) - 7)) 
Ok: 4 + 5 * 6 result: (4 + (5 * 6)) 
Ok: 1+2+3+4*5*6*-7+-8*+9-0 result: (1 + (2 + (3 + ((4 * (5 * (6 * -7))) + ((-8 * 9) - 0))))) 
Ok: (foo()) result: foo() 
Ok: foo() * ((1+2)+3*(2+3)) result: (foo() * ((1 + 2) + (3 * (2 + 3)))) 
Ok: (1+2)*3 result: ((1 + 2) * 3) 
Ok: 1+2*3 result: (1 + (2 * 3)) 
Fail: foo 

I Ich werde dies auf der Spirit-Mailingliste melden:

Verwandte Themen