代码之家  ›  专栏  ›  技术社区  ›  Pablo

为什么在boost spirit中使用流会严重影响性能?

  •  2
  • Pablo  · 技术社区  · 6 年前

    我准备了一个小的基准测试程序,用于测量不同的解析方法。当使用流和自定义函数将日期存储为time\t+double时,性能会大幅下降,这就是问题所在。

    string的奇怪的boost-spirit特性是因为seek回溯使用不匹配行的所有公共部分填充变量字符串,直到找到匹配的行。

    对于源代码质量(复制/粘贴、错误的变量名、弱缩进…)感到抱歉。我知道这个基准代码不会包含在干净的代码手册中,所以请忽略这个事实,让我们关注这个主题。

    我知道最快的方法是不使用回溯的字符串,但流的时间增量真的很奇怪。有人能解释一下发生了什么事吗?

    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/repository/include/qi_seek.hpp>
    #include <boost/chrono/chrono.hpp>
    #include <iomanip>
    #include <ctime>
    
    typedef std::string::const_iterator It;
    
    namespace structs {
        struct Timestamp {
            std::time_t date;
            double ms;
    
            friend std::istream& operator>> (std::istream& stream, Timestamp& time)
            {
                struct std::tm tm;
    
                if (stream >> std::get_time(&tm, "%Y-%b-%d %H:%M:%S") >> time.ms)
                    time.date = std::mktime(&tm);
    
                return stream;
            }
        };
    
        struct Record1 {
            std::string date;
            double time;
            std::string str;
        };
    
        struct Record2 {
            Timestamp date;
            double time;
            std::string str;
        };
    
        typedef std::vector<Record1> Records1;
        typedef std::vector<Record2> Records2;
    }
    
    BOOST_FUSION_ADAPT_STRUCT(structs::Record1,
            (std::string, date)
            (double, time)
            (std::string, str))
    
    BOOST_FUSION_ADAPT_STRUCT(structs::Record2,
            (structs::Timestamp, date)
            (double, time)
            (std::string, str))
    
    namespace boost { namespace spirit { namespace traits {
        template <typename It>
        struct assign_to_attribute_from_iterators<std::string, It, void> {
            static inline void call(It f, It l, std::string& attr) {
                attr = std::string(&*f, std::distance(f,l));
            }
        };
    } } }
    
    namespace qi = boost::spirit::qi;
    
    namespace QiParsers {
        template <typename It>
        struct Parser1 : qi::grammar<It, structs::Record1()>
        {
            Parser1() : Parser1::base_type(start) {
                using namespace qi;
    
                start = '[' >> raw[*~char_(']')] >> ']'
                    >> " - " >> double_ >> " s"
                    >> " => String: "  >> raw[+graph]
                    >> eol;
            }
    
        private:
            qi::rule<It, structs::Record1()> start;
        };
    
        template <typename It>
        struct Parser2 : qi::grammar<It, structs::Record2()>
        {
            Parser2() : Parser2::base_type(start) {
                using namespace qi;
    
                start = '[' >> stream >> ']'
                    >> " - " >> double_ >> " s"
                    >> " => String: "  >> raw[+graph]
                    >> eol;
            }
    
        private:
            qi::rule<It, structs::Record2()> start;
        };
    
        template <typename It>
        struct Parser3 : qi::grammar<It, structs::Records1()>
        {
            Parser3() : Parser3::base_type(start) {
                using namespace qi;
                using boost::phoenix::push_back;
    
                line = '[' >> raw[*~char_(']')] >> ']'
                    >> " - " >> double_ >> " s"
                    >> " => String: "  >> raw[+graph];
    
                ignore = *~char_("\r\n");
    
                start = (line[push_back(_val, _1)] | ignore) % eol;
            }
    
        private:
            qi::rule<It> ignore;
            qi::rule<It, structs::Record1()> line;
            qi::rule<It, structs::Records1()> start;
        };
    
        template <typename It>
        struct Parser4 : qi::grammar<It, structs::Records2()>
        {
            Parser4() : Parser4::base_type(start) {
                using namespace qi;
                using boost::phoenix::push_back;
    
                line = '[' >> stream >> ']'
                    >> " - " >> double_ >> " s"
                    >> " => String: "  >> raw[+graph];
    
                ignore = *~char_("\r\n");
    
                start = (line[push_back(_val, _1)] | ignore) % eol;
            }
    
        private:
            qi::rule<It> ignore;
            qi::rule<It, structs::Record2()> line;
            qi::rule<It, structs::Records2()> start;
        };
    }
    
    template<typename Parser, typename Container>
    Container parse_seek(It b, It e, const std::string& message)
    {
        static const Parser parser;
    
        Container records;
    
        boost::chrono::high_resolution_clock::time_point t0 = boost::chrono::high_resolution_clock::now();
        parse(b, e, *boost::spirit::repository::qi::seek[parser], records);
        boost::chrono::high_resolution_clock::time_point t1 = boost::chrono::high_resolution_clock::now();
    
        auto elapsed = boost::chrono::duration_cast<boost::chrono::milliseconds>(t1 - t0);
        std::cout << "Elapsed time: " << elapsed.count() << " ms (" << message << ")\n";
    
        return records;
    }
    
    template<typename Parser, typename Container>
    Container parse_ignoring(It b, It e, const std::string& message)
    {
        static const Parser parser;
    
        Container records;
    
        boost::chrono::high_resolution_clock::time_point t0 = boost::chrono::high_resolution_clock::now();
        parse(b, e, parser, records);
        boost::chrono::high_resolution_clock::time_point t1 = boost::chrono::high_resolution_clock::now();
    
        auto elapsed = boost::chrono::duration_cast<boost::chrono::milliseconds>(t1 - t0);
        std::cout << "Elapsed time: " << elapsed.count() << " ms (" << message << ")\n";
    
        return records;
    }
    
    static const std::string input1 = "[2018-Mar-01 00:00:00.000000] - 1.000 s => String: Valid_string\n";
    static const std::string input2 = "[2018-Mar-02 00:00:00.000000] - 2.000 s => I dont care\n";
    static std::string input("");
    
    int main() {
        const int N1 = 10;
        const int N2 = 100000;
    
        input.reserve(N1 * (input1.size() + N2*input2.size()));
    
        for (int i = N1; i--;)
        {
            input += input1;
    
            for (int j = N2; j--;)
                input += input2;
        }
    
        const auto records1 = parse_seek<QiParsers::Parser1<It>, structs::Records1>(input.begin(), input.end(), "std::string + seek");
        const auto records2 = parse_seek<QiParsers::Parser2<It>, structs::Records2>(input.begin(), input.end(), "stream + seek");
    
        const auto records3 = parse_ignoring<QiParsers::Parser3<It>, structs::Records1>(input.begin(), input.end(), "std::string + ignoring");
        const auto records4 = parse_ignoring<QiParsers::Parser4<It>, structs::Records2>(input.begin(), input.end(), "stream + ignoring");
    
        return 0;
    }
    

    控制台中的结果是:

    Elapsed time: 1445 ms (std::string + seek)
    Elapsed time: 21519 ms (stream + seek)
    Elapsed time: 860 ms (std::string + ignoring)
    Elapsed time: 19046 ms (stream + ignoring)
    
    2 回复  |  直到 6 年前
        1
  •  3
  •   sehe    6 年前

    好的,在发布的代码中,70%的时间花在流的下溢操作上。

    没有调查/为什么/那是,但是 写了一些天真的实现,看看我是否可以做得更好。第一步:

    ² 使现代化 我从那以后 analyzed it 并提供了 PR

    在这种特殊情况下,该PR产生的改进不会影响底线(参见 总结 )

    • operator>> 对于 Timestamp (我们不会使用它)
    • 替换的所有实例 '[' >> stream >> ']' 使用替代方案 '[' >> raw[*~char_(']')] >> ']' 因此,我们将始终使用trait将迭代器范围转换为属性类型( std::string 时间戳 )

    现在,我们实现 assign_to_attribute_from_iterators<structs::Timestamp, It> 特点:

    变体1:阵列源

    template <typename It>
    struct assign_to_attribute_from_iterators<structs::Timestamp, It, void> {
        static inline void call(It f, It l, structs::Timestamp& time) {
            boost::iostreams::stream<boost::iostreams::array_source> stream(f, l);
    
            struct std::tm tm;
            if (stream >> std::get_time(&tm, "%Y-%b-%d %H:%M:%S") >> time.ms)
                time.date = std::mktime(&tm);
            else throw "Parse failure";
        }
    };
    

    使用callgrind进行评测: (单击可缩放)

    它确实有了很大的改进,可能是因为我们假设底层的char缓冲区是连续的,而Spirit实现无法做出这种假设。我们花了大约42%的时间在 time_get

    粗略地说,25%的时间都花在现场工作上,其中令人担忧的是 约20% 用于执行动态强制转换:(

    变体2:重复使用的阵列源

    相同,但重用静态流实例以查看其是否有显著差异:

    static boost::iostreams::stream<boost::iostreams::array_source> s_stream;
    
    template <typename It>
    struct assign_to_attribute_from_iterators<structs::Timestamp, It, void> {
        static inline void call(It f, It l, structs::Timestamp& time) {
            struct std::tm tm;
    
            if (s_stream.is_open()) s_stream.close();
            s_stream.clear();
            boost::iostreams::array_source as(f, l);
            s_stream.open(as);
    
            if (s_stream >> std::get_time(&tm, "%Y-%b-%d %H:%M:%S") >> time.ms)
                time.date = std::mktime(&tm);
            else throw "Parse failure";
        }
    };
    

    分析显示没有显著差异)。

    变体3: strptime strtod / from_chars

    让我们看看降到C级是否会减少对语言环境的伤害:

    template <typename It>
    struct assign_to_attribute_from_iterators<structs::Timestamp, It, void> {
        static inline void call(It f, It l, structs::Timestamp& time) {
            struct std::tm tm;
            auto remain = strptime(&*f, "%Y-%b-%d %H:%M:%S", &tm);
            time.date = std::mktime(&tm);
    
        #if __has_include(<charconv>) || __cpp_lib_to_chars >= 201611
            auto result = std::from_chars(&*f, &*l, time.ms); // using <charconv> from c++17
        #else
            char* end;
            time.ms = std::strtod(remain, &end);
    
            assert(end > remain);
            static_cast<void>(l); // unused
        #endif
        }
    };
    

    如您所见,使用 字符串转换为浮点数 这里有点不太理想。输入范围是有界的,但无法判断 字符串转换为浮点数 关于这个。我无法分析 来自\u chars 这种方法更安全,因为它没有这个问题。

    在实践中,对于示例代码,使用它是安全的 字符串转换为浮点数 因为我们知道输入缓冲区是NUL终止的。

    在这里,您可以看到解析日期时间仍然是一个值得关注的因素:

    • mktime 15.58%
    • STRTIME 40.54%
    • 标准差5.88%

    但总的来说,现在的差别不那么严重了:

    • 解析器1:14.17%
    • 解析器2:43.44%
    • Parser3:5.69%
    • 解析器4:35.49%

    变体4:再次启动日期时间

    有趣的是,“低级别”C-API的性能离使用更高级别的Boost不远了 posix_time::ptime 功能:

    template <typename It>
    struct assign_to_attribute_from_iterators<structs::Timestamp, It, void> {
        static inline void call(It f, It l, structs::Timestamp& time) {
            time.date = to_time_t(boost::posix_time::time_from_string(std::string(f,l)));
        }
    };
    

    这可能会牺牲一些精度, according to the docs :

    enter image description here

    在这里,解析日期和时间所花费的总时间是68%。解析器的相对速度接近最后的速度:

    • 解析器1:12.33%
    • 解析器2:43.86%
    • 解析器3:5.22%
    • 解析器4:37.43%

    总结

    总之,结果表明,存储字符串似乎更快,即使您有可能分配更多字符串。我做了一个非常简单的检查 SSO 通过增加子字符串的长度:

    static const std::string input1 = "[2018-Mar-01 00:01:02.012345 THWARTING THE SMALL STRING OPTIMIZATION HERE THIS WON'T FIT, NO DOUBT] - 1.000 s => String: Valid_string\n";
    static const std::string input2 = "[2018-Mar-02 00:01:02.012345 THWARTING THE SMALL STRING OPTIMIZATION HERE THIS WON'T FIT, NO DOUBT] - 2.000 s => I dont care\n";
    

    没有重大影响,因此只剩下解析本身。

    很明显,要么您想要延迟解析时间( Parser3 是目前为止最快的) 应该与久经考验的助力相匹配 posix_time 功能。

    表册

    下面是我使用的组合基准代码。一些事情发生了变化:

    • 添加了一些健全性检查输出(以避免测试无意义的代码)
    • 使迭代器成为泛型(更改为 char* 对优化构建中的性能没有显著影响)
    • 上述变量都可以通过更改 #if 1 #if 0 在正确的地点
    • 为方便起见,减少了N1/N2

    我经常使用C++14,因为代码的目的是找到瓶颈。在分析之后,获得的任何智慧都可以相对容易地进行后传。

    Live On Coliru

    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/repository/include/qi_seek.hpp>
    #include <boost/date_time/posix_time/posix_time.hpp>
    #include <boost/chrono/chrono.hpp>
    #include <iomanip>
    #include <ctime>
    #if __has_include(<charconv>) || __cpp_lib_to_chars >= 201611
    #    include <charconv> // not supported yet until GCC 8
    #endif
    
    namespace structs {
        struct Timestamp {
            std::time_t date;
            double ms;
        };
    
        struct Record1 {
            std::string date;
            double time;
            std::string str;
        };
    
        struct Record2 {
            Timestamp date;
            double time;
            std::string str;
        };
    
        typedef std::vector<Record1> Records1;
        typedef std::vector<Record2> Records2;
    }
    
    BOOST_FUSION_ADAPT_STRUCT(structs::Record1,
            (std::string, date)
            (double, time)
            (std::string, str))
    
    BOOST_FUSION_ADAPT_STRUCT(structs::Record2,
            (structs::Timestamp, date)
            (double, time)
            (std::string, str))
    
    namespace boost { namespace spirit { namespace traits {
        template <typename It>
        struct assign_to_attribute_from_iterators<std::string, It, void> {
            static inline void call(It f, It l, std::string& attr) {
                attr = std::string(&*f, std::distance(f,l));
            }
        };
    
        static boost::iostreams::stream<boost::iostreams::array_source> s_stream;
    
        template <typename It>
        struct assign_to_attribute_from_iterators<structs::Timestamp, It, void> {
            static inline void call(It f, It l, structs::Timestamp& time) {
    #if 1
                time.date = to_time_t(boost::posix_time::time_from_string(std::string(f,l)));
    #elif 1
                struct std::tm tm;
                boost::iostreams::stream<boost::iostreams::array_source> stream(f, l);
    
                if (stream >> std::get_time(&tm, "%Y-%b-%d %H:%M:%S") >> time.ms)
                    time.date = std::mktime(&tm);
                else
                    throw "Parse failure";
    #elif 1
                struct std::tm tm;
                if (s_stream.is_open()) s_stream.close();
                s_stream.clear();
                boost::iostreams::array_source as(f, l);
                s_stream.open(as);
    
                if (s_stream >> std::get_time(&tm, "%Y-%b-%d %H:%M:%S") >> time.ms)
                    time.date = std::mktime(&tm);
                else
                    throw "Parse failure";
    #else
                struct std::tm tm;
                auto remain = strptime(&*f, "%Y-%b-%d %H:%M:%S", &tm);
                time.date = std::mktime(&tm);
    
            #if __has_include(<charconv>) || __cpp_lib_to_chars >= 201611
                auto result = std::from_chars(&*f, &*l, time.ms); // using <charconv> from c++17
            #else
                char* end;
                time.ms = std::strtod(remain, &end);
    
                assert(end > remain);
                static_cast<void>(l); // unused
            #endif
    #endif
            }
        };
    } } }
    
    namespace qi = boost::spirit::qi;
    
    namespace QiParsers {
        template <typename It>
        struct Parser1 : qi::grammar<It, structs::Record1()>
        {
            Parser1() : Parser1::base_type(start) {
                using namespace qi;
    
                start = '[' >> raw[*~char_(']')] >> ']'
                    >> " - " >> double_ >> " s"
                    >> " => String: "  >> raw[+graph]
                    >> eol;
            }
    
        private:
            qi::rule<It, structs::Record1()> start;
        };
    
        template <typename It>
        struct Parser2 : qi::grammar<It, structs::Record2()>
        {
            Parser2() : Parser2::base_type(start) {
                using namespace qi;
    
                start = '[' >> raw[*~char_(']')] >> ']'
                    >> " - " >> double_ >> " s"
                    >> " => String: "  >> raw[+graph]
                    >> eol;
            }
    
        private:
            qi::rule<It, structs::Record2()> start;
        };
    
        template <typename It>
        struct Parser3 : qi::grammar<It, structs::Records1()>
        {
            Parser3() : Parser3::base_type(start) {
                using namespace qi;
                using boost::phoenix::push_back;
    
                line = '[' >> raw[*~char_(']')] >> ']'
                    >> " - " >> double_ >> " s"
                    >> " => String: "  >> raw[+graph];
    
                ignore = *~char_("\r\n");
    
                start = (line[push_back(_val, _1)] | ignore) % eol;
            }
    
        private:
            qi::rule<It> ignore;
            qi::rule<It, structs::Record1()> line;
            qi::rule<It, structs::Records1()> start;
        };
    
        template <typename It>
        struct Parser4 : qi::grammar<It, structs::Records2()>
        {
            Parser4() : Parser4::base_type(start) {
                using namespace qi;
                using boost::phoenix::push_back;
    
                line = '[' >> raw[*~char_(']')] >> ']'
                    >> " - " >> double_ >> " s"
                    >> " => String: "  >> raw[+graph];
    
                ignore = *~char_("\r\n");
    
                start = (line[push_back(_val, _1)] | ignore) % eol;
            }
    
        private:
            qi::rule<It> ignore;
            qi::rule<It, structs::Record2()> line;
            qi::rule<It, structs::Records2()> start;
        };
    }
    
    template <typename Parser> static const Parser s_instance {};
    
    template<template <typename> class Parser, typename Container, typename It>
    Container parse_seek(It b, It e, const std::string& message)
    {
        Container records;
    
        auto const t0 = boost::chrono::high_resolution_clock::now();
        parse(b, e, *boost::spirit::repository::qi::seek[s_instance<Parser<It> >], records);
        auto const t1 = boost::chrono::high_resolution_clock::now();
    
        auto elapsed = boost::chrono::duration_cast<boost::chrono::milliseconds>(t1 - t0);
        std::cout << "Elapsed time: " << elapsed.count() << " ms (" << message << ")\n";
    
        return records;
    }
    
    template<template <typename> class Parser, typename Container, typename It>
    Container parse_ignoring(It b, It e, const std::string& message)
    {
        Container records;
    
        auto const t0 = boost::chrono::high_resolution_clock::now();
        parse(b, e, s_instance<Parser<It> >, records);
        auto const t1 = boost::chrono::high_resolution_clock::now();
    
        auto elapsed = boost::chrono::duration_cast<boost::chrono::milliseconds>(t1 - t0);
        std::cout << "Elapsed time: " << elapsed.count() << " ms (" << message << ")\n";
    
        return records;
    }
    
    static const std::string input1 = "[2018-Mar-01 00:01:02.012345] - 1.000 s => String: Valid_string\n";
    static const std::string input2 = "[2018-Mar-02 00:01:02.012345] - 2.000 s => I dont care\n";
    
    std::string prepare_input() {
        std::string input;
        const int N1 = 10;
        const int N2 = 1000;
    
        input.reserve(N1 * (input1.size() + N2*input2.size()));
    
        for (int i = N1; i--;) {
            input += input1;
            for (int j = N2; j--;)
                input += input2;
        }
    
        return input;
    }
    
    int main() {
        auto const input = prepare_input();
    
        auto f = input.data(), l = f + input.length();
    
        for (auto& r: parse_seek<QiParsers::Parser1, structs::Records1>(f, l, "std::string + seek")) {
            std::cout << r.date << "\n";
            break;
        }
        for (auto& r: parse_seek<QiParsers::Parser2, structs::Records2>(f, l, "stream + seek")) {
            auto tm = *std::localtime(&r.date.date);
            std::cout << std::put_time(&tm, "%Y-%b-%d %H:%M:%S") << "\n";
            break;
        }
        for (auto& r: parse_ignoring<QiParsers::Parser3, structs::Records1>(f, l, "std::string + ignoring")) {
            std::cout << r.date << "\n";
            break;
        }
        for (auto& r: parse_ignoring<QiParsers::Parser4, structs::Records2>(f, l, "stream + ignoring")) {
            auto tm = *std::localtime(&r.date.date);
            std::cout << std::put_time(&tm, "%Y-%b-%d %H:%M:%S") << "\n";
            break;
        }
    }
    

    打印类似的内容

    Elapsed time: 14 ms (std::string + seek)
    2018-Mar-01 00:01:02.012345
    Elapsed time: 29 ms (stream + seek)
    2018-Mar-01 00:01:02
    Elapsed time: 2 ms (std::string + ignoring)
    2018-Mar-01 00:01:02.012345
    Elapsed time: 22 ms (stream + ignoring)
    2018-Mar-01 00:01:02
    

    所有百分比都是相对于 全部的 项目成本。那个 扭曲百分比(如果不考虑非流解析器测试,提到的70%会更糟),但这些数字对于相对比较来说是一个足够好的指南 在内部 试运行。

        2
  •  1
  •   sehe    6 年前

    这个 stream 解析器最终会执行以下操作:

        template <typename Iterator, typename Context
          , typename Skipper, typename Attribute>
        bool parse(Iterator& first, Iterator const& last
          , Context& /*context*/, Skipper const& skipper
          , Attribute& attr_) const
        {
            typedef qi::detail::iterator_source<Iterator> source_device;
            typedef boost::iostreams::stream<source_device> instream;
    
            qi::skip_over(first, last, skipper);
    
            instream in(first, last);           // copies 'first'
            in >> attr_;                        // use existing operator>>()
    
            // advance the iterator if everything is ok
            if (in) {
                if (!in.eof()) {
                    std::streamsize pos = in.tellg();
                    std::advance(first, pos);
                } else {
                    first = last;
                }
                return true;
            }
    
            return false;
        }
    

    这个 detail::iterator_source<Iterator> 设备是一个代价高昂的抽象,因为它需要是通用的。它需要能够支持仅向前的迭代器。

    专门用于随机访问迭代器

    我创建了一个Pull请求,专门处理随机访问迭代器: https://github.com/boostorg/spirit/pull/383 哪个专业 iterator_source 对于随机访问迭代器:

    std::streamsize read (char_type* s, std::streamsize n)
    {
        if (first == last)
            return -1;
    
        n = std::min(std::distance(first, last), n);
    
        // copy_n is only part of c++11, so emulate it
        std::copy(first, first + n, s);
        first += n;
        pos += n;
    
        return n;
    }
    

    如果没有专业化,我们可以观察到这些时间安排: Interactive Plot.ly

    (100个样本,置信区间0.95)

    benchmarking std::string + seek     mean:   31.9222 ms std dev: 228.862  μs
    benchmarking std::string + ignoring mean:   16.1855 ms std dev: 257.903  μs
    
    benchmarking stream      + seek     mean: 1075.46   ms std dev:  22.23   ms
    benchmarking stream      + ignoring mean: 1064.41   ms std dev:  26.7218 ms
    

    enter image description here

    通过专业化,我们可以观察到这些时间安排: Interactive Plot.ly

    benchmarking std::string + seek     mean:  31.8703 ms std dev: 529.196  μs
    benchmarking std::string + ignoring mean:  15.913  ms std dev: 848.514  μs
    
    benchmarking stream      + seek     mean: 436.263  ms std dev:  19.4035 ms
    benchmarking stream      + ignoring mean: 419.539  ms std dev:  20.0511 ms
    

    enter image description here

    Nonius基准代码

    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/repository/include/qi_seek.hpp>
    #include <boost/chrono/chrono.hpp>
    #include <iomanip>
    #include <ctime>
    
    namespace structs {
        struct Timestamp {
            std::time_t date;
            double ms;
    
            friend std::istream& operator>> (std::istream& stream, Timestamp& time)
            {
                struct std::tm tm;
    
                if (stream >> std::get_time(&tm, "%Y-%b-%d %H:%M:%S") >> time.ms)
                    time.date = std::mktime(&tm);
    
                return stream;
            }
        };
    
        struct Record1 {
            std::string date;
            double time;
            std::string str;
        };
    
        struct Record2 {
            Timestamp date;
            double time;
            std::string str;
        };
    
        typedef std::vector<Record1> Records1;
        typedef std::vector<Record2> Records2;
    }
    
    BOOST_FUSION_ADAPT_STRUCT(structs::Record1,
            (std::string, date)
            (double, time)
            (std::string, str))
    
    BOOST_FUSION_ADAPT_STRUCT(structs::Record2,
            (structs::Timestamp, date)
            (double, time)
            (std::string, str))
    
    namespace boost { namespace spirit { namespace traits {
        template <typename It>
        struct assign_to_attribute_from_iterators<std::string, It, void> {
            static inline void call(It f, It l, std::string& attr) {
                attr = std::string(&*f, std::distance(f,l));
            }
        };
    } } }
    
    namespace qi = boost::spirit::qi;
    
    namespace QiParsers {
        template <typename It>
        struct Parser1 : qi::grammar<It, structs::Record1()>
        {
            Parser1() : Parser1::base_type(start) {
                using namespace qi;
    
                start = '[' >> raw[*~char_(']')] >> ']'
                    >> " - " >> double_ >> " s"
                    >> " => String: "  >> raw[+graph]
                    >> eol;
            }
    
        private:
            qi::rule<It, structs::Record1()> start;
        };
    
        template <typename It>
        struct Parser2 : qi::grammar<It, structs::Record2()>
        {
            Parser2() : Parser2::base_type(start) {
                using namespace qi;
    
                start = '[' >> stream >> ']'
                    >> " - " >> double_ >> " s"
                    >> " => String: "  >> raw[+graph]
                    >> eol;
            }
    
        private:
            qi::rule<It, structs::Record2()> start;
        };
    
        template <typename It>
        struct Parser3 : qi::grammar<It, structs::Records1()>
        {
            Parser3() : Parser3::base_type(start) {
                using namespace qi;
                using boost::phoenix::push_back;
    
                line = '[' >> raw[*~char_(']')] >> ']'
                    >> " - " >> double_ >> " s"
                    >> " => String: "  >> raw[+graph];
    
                ignore = *~char_("\r\n");
    
                start = (line[push_back(_val, _1)] | ignore) % eol;
            }
    
        private:
            qi::rule<It> ignore;
            qi::rule<It, structs::Record1()> line;
            qi::rule<It, structs::Records1()> start;
        };
    
        template <typename It>
        struct Parser4 : qi::grammar<It, structs::Records2()>
        {
            Parser4() : Parser4::base_type(start) {
                using namespace qi;
                using boost::phoenix::push_back;
    
                line = '[' >> stream >> ']'
                    >> " - " >> double_ >> " s"
                    >> " => String: "  >> raw[+graph];
    
                ignore = *~char_("\r\n");
    
                start = (line[push_back(_val, _1)] | ignore) % eol;
            }
    
        private:
            qi::rule<It> ignore;
            qi::rule<It, structs::Record2()> line;
            qi::rule<It, structs::Records2()> start;
        };
    }
    
    typedef boost::chrono::high_resolution_clock::time_point time_point;
    
    template<template <typename> class Parser, typename Container, typename It>
    Container parse_seek(It b, It e, const std::string& message)
    {
        static const Parser<It> s_instance;
        Container records;
    
        time_point const t0 = boost::chrono::high_resolution_clock::now();
        parse(b, e, *boost::spirit::repository::qi::seek[s_instance], records);
        time_point const t1 = boost::chrono::high_resolution_clock::now();
    
        std::cout << "Elapsed time: " << boost::chrono::duration_cast<boost::chrono::milliseconds>(t1 - t0).count() << " ms (" << message << ")\n";
    
        return records;
    }
    
    template<template <typename> class Parser, typename Container, typename It>
    Container parse_ignoring(It b, It e, const std::string& message)
    {
        static const Parser<It> s_instance;
        Container records;
    
        time_point const t0 = boost::chrono::high_resolution_clock::now();
        parse(b, e, s_instance, records);
        time_point const t1 = boost::chrono::high_resolution_clock::now();
    
        std::cout << "Elapsed time: " << boost::chrono::duration_cast<boost::chrono::milliseconds>(t1 - t0).count() << " ms (" << message << ")\n";
    
        return records;
    }
    
    static const std::string input1 = "[2018-Mar-01 00:01:02.012345] - 1.000 s => String: Valid_string\n";
    static const std::string input2 = "[2018-Mar-02 00:01:02.012345] - 2.000 s => I dont care\n";
    
    std::string prepare_input() {
        std::string input;
        const int N1 = 10;
        const int N2 = 10000;
    
        input.reserve(N1 * (input1.size() + N2*input2.size()));
    
        for (int i = N1; i--;) {
            input += input1;
            for (int j = N2; j--;)
                input += input2;
        }
    
        return input;
    }
    
    void verify(structs::Records1 const& records) {
        if (records.empty())
            std::cout << "Oops nothing parsed\n";
        else {
            structs::Record1 const& r = *records.begin();
            std::cout << r.date << "\n";
        }
    }
    
    void verify(structs::Records2 const& records) {
        if (records.empty())
            std::cout << "Oops nothing parsed\n";
        else {
            structs::Record2 const& r = *records.begin();
            auto tm = *std::localtime(&r.date.date);
            std::cout << std::put_time(&tm, "%Y-%b-%d %H:%M:%S") << " " << r.date.ms << "\n";
        }
    }
    
    static std::string const input = prepare_input();
    
    #define NONIUS_RUNNER
    #include <nonius/benchmark.h++>
    #include <nonius/main.h++>
    
    NONIUS_BENCHMARK("std::string + seek", [] {
        char const* f = input.data();
        char const* l = f + input.length();
        //std::string::const_iterator f = input.begin(), l = input.end();
        verify(parse_seek<QiParsers::Parser1,     structs::Records1>(f, l, "std::string + seek"));
    })
    
    NONIUS_BENCHMARK("stream + seek", [] {
        char const* f = input.data();
        char const* l = f + input.length();
        //std::string::const_iterator f = input.begin(), l = input.end();
        verify(parse_seek<QiParsers::Parser2,     structs::Records2>(f, l, "stream + seek"));
    })
    
    NONIUS_BENCHMARK("std::string + ignoring", [] {
        char const* f = input.data();
        char const* l = f + input.length();
        //std::string::const_iterator f = input.begin(), l = input.end();
        verify(parse_ignoring<QiParsers::Parser3, structs::Records1>(f, l, "std::string + ignoring"));
    })
    
    NONIUS_BENCHMARK("stream + ignoring", [] {
        char const* f = input.data();
        char const* l = f + input.length();
        //std::string::const_iterator f = input.begin(), l = input.end();
        verify(parse_ignoring<QiParsers::Parser4, structs::Records2>(f, l, "stream + ignoring"));
    })
    

    e、 g.当解析器的迭代器恰好是 multi_pass_adaptor<InputIterator> ,这是一个独立的怪物: Boost spirit memory leak