Рекомендации по производительности Boost Spirit (анализ даты)

Я реорганизовал (пока изучал) некоторые существующие процедуры синтаксического анализа даты с ручным массажем с помощью Boost Spirit. Моя основная цель состояла в том, чтобы попытаться создать единый и унифицированный интерфейс, чтобы иметь возможность анализировать даты, представленные множеством различных способов. Раньше у меня было несколько разных сигнатур функций для преобразования различных форматов даты (таких как parseDateYYYYMMDD и parseDateDDMMYYYY или parseDateStrangeFormatXYZ) в объекты даты, специфичные для домена. Кажется, что с помощью Spirit я могу создать основную грамматику, которая способна объединить все эти форматы вместе, и будет только один parseDate

К сожалению, когда я сравнивал производительность реализации Boost Spirit с кодом, обработанным вручную, производительность была примерно в 5 раз медленнее (64-битная сборка в режиме Release) даже с самым обычным форматом даты YYYY-MM-dd hh:mm:ss.zzz, в котором не должно быть никакого обратного отслеживания . Я надеюсь, что смогу хотя бы приблизиться к старому коду. Я хотел бы получить некоторую обратную связь, есть ли большие возможности для улучшения, чтобы улучшить грамматику (и, возможно, AST) таким образом, чтобы это было оптимальным для производительности. В настоящее время я собираю части даты в вектор, и после успешной работы парсера я присоединяю семантическое действие для создания объектов Date с компонентами, собранными из вектора. Может, здесь есть одно слабое место? Возможно ли много выделения / освобождения внутри грамматики?

Объекты My Date должны быть конструкторами

// Date without time part
Date(Day d, Month m, Year y)
// Date with time part
Date(Day d, Month m, Year y, Size hours, Size minutes, Size seconds)

Вот фрагмент для синтаксического анализа (он находится в файле .cpp)

namespace {

namespace x3 = boost::spirit::x3;

namespace parsers {

    template<typename T>
    auto as = [](auto p) { return x3::rule<struct _, T>{} %= x3::as_parser(p); };

    auto kwd = [](auto p, auto q) { return p > q; };

    static const struct months : x3::symbols<int> {
        months() {
            add
                ("Jan"          , 1)
                ("Feb"          , 2)
                ("Mar"          , 3)
                ("Apr"          , 4)
                ("May"          , 5)
                ("Jun"          , 6)
                ("Jul"          , 7)
                ("Aug"          , 8)
                ("Sep"          , 9)
                ("Oct"          , 10)
                ("Nov"          , 11)
                ("Dec"          , 12)
                ("January"      , 1)
                ("February"     , 2)
                ("March"        , 3)
                ("April"        , 4)
                ("May"          , 5)
                ("June"         , 6)
                ("July"         , 7)
                ("August"       , 8)
                ("September"    , 9)
                ("October"      , 10)
                ("November"     , 11)
                ("December"     , 12)
                ;
        }
    } months;

    static const x3::uint_parser<int, 10, 4, 4> yyyy;
    static const x3::uint_parser<int, 10, 1, 2> MM, dd;
    static const x3::uint_parser<int, 10, 2, 2> H_mm_ss;
    static const x3::uint_parser<int, 10, 3, 3> zzz;

    // ADL markers

    struct ql_date_class                          {};
    struct ql_date_time_class                     {};

    static const auto ql_date       = x3::rule<ql_date_class     , Date>{"ql-date"};
    static const auto ql_date_time  = x3::rule<ql_date_time_class, Date>{"ql-date-time"};

    auto validate_H = [](auto& ctx) { 
        const auto& x = x3::_attr(ctx);
        x3::_pass(ctx) = (x >= 0 && x < 24);
    };

    auto validate_mm_ss = [](auto& ctx) { 
        const auto& x = x3::_attr(ctx);
        x3::_pass(ctx) = (x >= 0 && x < 60);
    };

    auto validate_yyyy = [](auto& ctx) { 
        const auto& x = x3::_attr(ctx);
        x3::_pass(ctx) = (x > 1900 && x < 2200);
    };

    auto validate_MM = [](auto& ctx) { 
        const auto& x = x3::_attr(ctx);
        x3::_pass(ctx) = (x >= 1 && x <= 12);
    };

    auto validate_dd = [](auto& ctx) { 
        const auto& x = x3::_attr(ctx);
        x3::_pass(ctx) = (x >= 1 && x <= 31);
    };

    static const auto year_ = yyyy[validate_yyyy];
    static const auto month_ = as<int>(months | MM[validate_MM]);
    static const auto day_ = dd[validate_dd];

    static const auto hours_ = H_mm_ss[validate_H];
    static const auto minutes_ = H_mm_ss[validate_mm_ss];
    static const auto seconds_ = H_mm_ss[validate_mm_ss];
    static const auto milliseconds_ = zzz;

    auto date_parser = as<std::vector<int>>(
        year_ >
        as<std::vector<int>>(
            kwd('-',  as<std::vector<int>>(month_ >  '-' > day_))
            |
            kwd('.',  as<std::vector<int>>(month_ >  '.' > day_))
            |
            kwd('/',  as<std::vector<int>>(month_ >  '/' > day_))
        )
        |
        day_ >
        as<std::vector<int>>(
            kwd('-',  as<std::vector<int>>(month_ >  '-' > year_))
            |
            kwd('.',  as<std::vector<int>>(month_ >  '.' > year_))
            |
            kwd('/',  as<std::vector<int>>(month_ >  '/' > year_))
        )[([](auto& ctx) { std::swap(x3::_attr(ctx)[0], x3::_attr(ctx)[2]); })]
    )
    ;

    static const auto time_parser = as<std::vector<int>>(
        hours_ >
        as<std::vector<int>>(
            (
                ':' >  minutes_ > 
                as<std::vector<int>>(
                    (
                        ':' > seconds_ >
                        as<int>(
                            '.' > milliseconds_ 
                            |
                            x3::attr(int(0))
                        )
                    )
                    |
                    (
                        x3::repeat(2)[x3::attr(int(0))]
                    )
                )
            )
            |
            (
                minutes_ > 
                as<std::vector<int>>(
                    (
                        seconds_ >
                        as<int>(
                            milliseconds_ 
                            |
                            x3::attr(int(0))
                        )
                    )
                    |
                    (
                        x3::repeat(2)[x3::attr(int(0))]
                    )
                )
            )
        )
    )
    ;

    auto make_ql_date = [](auto& ctx) {
        x3::_val(ctx) = Date(
            x3::_attr(ctx)[2],
            static_cast<Month>(x3::_attr(ctx)[1]),
            x3::_attr(ctx)[0]
        ); 
    };

    auto make_ql_date_time = [](auto& ctx) {
        using boost::fusion::at_c;
        x3::_val(ctx) = Date(
            at_c<0>(x3::_attr(ctx))[2],
            static_cast<Month>(at_c<0>(x3::_attr(ctx))[1]),
            at_c<0>(x3::_attr(ctx))[0],
            at_c<1>(x3::_attr(ctx))[0],
            at_c<1>(x3::_attr(ctx))[1],
            at_c<1>(x3::_attr(ctx))[2],
            at_c<1>(x3::_attr(ctx))[3]
        ); 
    };

    static const auto ql_date_def = 
        date_parser[make_ql_date]
        ;

    static const auto ql_date_time_def = 
        (
            date_parser > 
            as<std::vector<int>>(    
                (
                    x3::no_skip[x3::omit[x3::char_('T') | ' ' ]] >
                    time_parser
                )
                |
                (
                    x3::repeat(4)[x3::attr(int(0))]               
                )
            )
        )[make_ql_date_time] 
        ;

    BOOST_SPIRIT_DEFINE(
        ql_date,
        ql_date_time
    )

    auto try_parse = [](const std::string& date, const auto& p) 
        -> boost::optional<Date> 
    {
        auto ast = Date();
        auto first = date.begin();
        const auto last = date.end();
        boost::spirit::x3::ascii::space_type space;
        bool r = phrase_parse(first, last, p, space, ast);
        if (!r || first != last) {
            return boost::none;
        }
        else {
            return ast;
        }
    };

    auto parse = [](const std::string& date, const auto& p) {
        auto ast = Date();
        auto first = date.begin();
        const auto last = date.end();
        boost::spirit::x3::ascii::space_type space;
        bool r = phrase_parse(first, last, parsers::ql_date, space, ast);
        QL_REQUIRE(r && first == last,
            "Parsing of " << date << " failed at " << std::string(first, last));
        return ast;
    };
} // namespace parsers

} // namespace anonymous

Вот бесплатные функции, которые используют парсеры для формирования объектов Date.


boost::optional<Date> DateTimeParser::maybeParseDate(const std::string& date) {
    return parsers::try_parse(date, parsers::ql_date);
}

Date DateTimeParser::parseDate(const std::string& date) {
    return parsers::parse(date, parsers::ql_date);
}

boost::optional<Date> 
DateTimeParser::maybeParseDateTime(const std::string& date) {
    return parsers::try_parse(date, parsers::ql_date_time);
}

Date DateTimeParser::parseDateTime(const std::string& date) {
    return parsers::parse(date, parsers::ql_date_time);
}


person Lauri    schedule 03.02.2020    source источник
comment
Spirit не побьет рукописный / оптимизированный синтаксический анализатор, но в вашем случае есть 1) std::vector<int> возня (преобразование, добавление) 2) x3::symbols, что не самое быстрое занятие. Кроме того, в качестве бонуса имейте в виду, что x3::char_('abcde') будет производить более медленный синтаксический анализатор, чем переключатель, написанный вручную.   -  person Nikita Kniazev    schedule 03.02.2020
comment
Лаури, не могли бы вы опубликовать минимальный код, который можно скомпилировать. Код не будет компилироваться, потому что QL_REQUIRE не определен, и нет основной программы, и DateTimeParser не определен.   -  person user1681377    schedule 08.02.2020
comment
Код на coliru.stacked-crooked.com/a/322dd5c5a6683117 компилируется и запускается. Я слышал, что семантические действия выполняются медленнее, чем автоматическое распространение атрибутов. Будет ли использование BOOST_FUSION_ADAPT_STRUCT избежать копирования из std :: vector ‹int› в Date?   -  person user1681377    schedule 08.02.2020
comment
Код использует как ‹std :: vector ‹int›› (...) несколько мест, когда неясно, по крайней мере, для меня, зачем это нужно. Кроме того, код использует BOOST_SPIRIT_DEFINE, когда, AFAICT, поскольку в грамматике нет рекурсии, в этом нет необходимости. Не могли бы вы прояснить, @Lauri, почему их так много, как ‹T› и любые BOOST_SPIRIT_DEFINE?   -  person user1681377    schedule 11.02.2020


Ответы (1)


Lauri, посмотрите, пожалуйста, date_parser gist работает достаточно быстро. Это позволяет избежать ненужного копирования до самого последнего момента, когда для копирования из атрибута парсера в структуру Date используется сложный код attr2date.

Некоторые могут подумать, что использование BOOST_FUSION_ADAPT_STRUCT может избежать необходимости в attr2date; однако эта попытка была предпринята и привела к ошибке времени компиляции. Это можно увидеть, переключив #define в USE_ALTERNATIVES, ="norefrom_propagation-cppollow .

HTH.

-Ларри

person user1681377    schedule 13.02.2020
comment
Хотя предоставленный мной код работает, он слишком сложен. Конечно, более опытный духовный программист (@sehe?) Мог бы предложить более простое решение? - person user1681377; 26.06.2020