Вопросы производительности Boost Spirit (разбор даты) - PullRequest
0 голосов
/ 03 февраля 2020

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

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

Мои объекты 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 file)

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);
}

1 Ответ

0 голосов
/ 13 февраля 2020

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

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

HTH.

-Larry

...