【问题标题】:Boost Spirit: slow parsing optimizationBoost Spirit:慢解析优化
【发布时间】:2016-01-03 06:41:34
【问题描述】:

我是 Spirit 和 Boost 的新手。我正在尝试解析 VRML 文件的一部分,如下所示:

point
            [
            #coordinates written in meters.
            -3.425386e-001 -1.681608e-001 0.000000e+000,
            -3.425386e-001 -1.642545e-001 0.000000e+000,
            -3.425386e-001 -1.603483e-001 0.000000e+000,

# 开头的注释是可选的。

我已经编写了一个语法,它工作正常,但解析过程需要很长时间。我想优化它以运行得更快。我的代码如下所示:

struct Point
{
    double a;
    double b;
    double c;

    Point() : a(0.0), b(0.0), c(0.0){}
};

BOOST_FUSION_ADAPT_STRUCT
(
    Point,
    (double, a) 
    (double, b)
    (double, c)
)

namespace qi   = boost::spirit::qi;
namespace repo = boost::spirit::repository;

template <typename Iterator>
struct PointParser : 
public qi::grammar<Iterator, std::vector<Point>(), qi::space_type>
{
   PointParser() : PointParser::base_type(start, "PointGrammar")
   {
      singlePoint = qi::double_>>qi::double_>>qi::double_>>*qi::lit(",");
      comment = qi::lit("#")>>*(qi::char_("a-zA-Z.") - qi::eol);
      prefix = repo::seek[qi::lexeme[qi::skip[qi::lit("point")>>qi::lit("[")>>*comment]]];
      start %= prefix>>qi::repeat[singlePoint];     

      //BOOST_SPIRIT_DEBUG_NODES((prefix)(comment)(singlePoint)(start));
   }

   qi::rule<Iterator, Point(), qi::space_type>              singlePoint;
   qi::rule<Iterator, qi::space_type>                       comment;
   qi::rule<Iterator, qi::space_type>                       prefix;
   qi::rule<Iterator, std::vector<Point>(), qi::space_type> start;
};

我打算解析的部分位于输入文本的中间,因此我需要跳过文本部分才能到达它。我使用 repo::seek 实现了它。这是最好的方法吗?

我按以下方式运行解析器:

std::vector<Point> points;
typedef PointParser<std::string::const_iterator> pointParser;   
pointParser g2; 

auto start = ch::high_resolution_clock::now();  
bool r = phrase_parse(Data.begin(), Data.end(), g2, qi::space, points); 
auto end = ch::high_resolution_clock::now();

auto duration = ch::duration_cast<boost::chrono::milliseconds>(end - start).count();

要解析输入文本中的大约 80k 个条目,大约需要 2.5 秒,这对于我的需要来说相当慢。我的问题是有没有办法以更优化的方式编写解析规则以使其(更快)更快?我一般如何改进这个实现?

我是 Spirit 的新手,因此非常感谢您提供一些解释。

【问题讨论】:

  • 如果相同的代码在类似系统(球场)和类似输入上的运行时间在 36 毫秒到 2.5 秒之间,我敢打赌后者没有启用优化。
  • 对此不确定。我正在使用带有 VS2013 的 boost 1.59 库的预编译版本。你能建议任何优化标志吗?
  • Boost(.Spirit) 非常依赖模板,预编译的库不会拖慢您的速度...为什么您认为 您的 代码编译如此缓慢? Spirit 是在您使用时编译的,而不是预先编译的。您需要确保您正在定时发布您的代码。
  • 哇!有什么不同。在 debug 中花费了 2462 毫秒,但在 release 中花费了 17 毫秒!非常感谢。

标签: c++ parsing boost-spirit qi


【解决方案1】:

我已将您的语法与Nonius 基准测试挂钩,并生成约85k 行的均匀随机输入数据(下载:http://stackoverflow-sehe.s3.amazonaws.com/input.txt,7.4 MB)。

  • 您是否在发布版本中测量时间?
  • 您是否使用慢速文件输入?

在预先读取文件时,我总是会花 ~36ms 的时间来解析整个文件。

clock resolution: mean is 17.616 ns (40960002 iterations)

benchmarking sample
collecting 100 samples, 1 iterations each, in estimated 3.82932 s
mean: 36.0971 ms, lb 35.9127 ms, ub 36.4456 ms, ci 0.95
std dev: 1252.71 μs, lb 762.716 μs, ub 2.003 ms, ci 0.95
found 6 outliers among 100 samples (6%)
variance is moderately inflated by outliers

代码:见下文。


注意事项:

  • 您似乎对使用船长和一起寻找有冲突。我建议你简化prefix:

    comment     = '#' >> *(qi::char_ - qi::eol);
    
    prefix      = repo::seek[
                      qi::lit("point") >> '[' >> *comment
                  ];
    

    prefix 将使用空格跳过,并忽略任何匹配的属性(因为规则声明的类型)。通过从规则声明中删除船长,使comment 隐式成为一个词素

        // implicit lexeme:
        qi::rule<Iterator>                       comment;
    

    注意请参阅Boost spirit skipper issues了解更多背景信息。

Live On Coliru

#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/repository/include/qi_seek.hpp>

namespace qi   = boost::spirit::qi;
namespace repo = boost::spirit::repository;

struct Point { double a = 0, b = 0, c = 0; };

BOOST_FUSION_ADAPT_STRUCT(Point, a, b, c)

template <typename Iterator>
struct PointParser : public qi::grammar<Iterator, std::vector<Point>(), qi::space_type>
{
    PointParser() : PointParser::base_type(start, "PointGrammar")
    {
        singlePoint = qi::double_ >> qi::double_ >> qi::double_ >> *qi::lit(',');

        comment     = '#' >> *(qi::char_ - qi::eol);

        prefix      = repo::seek[
            qi::lit("point") >> '[' >> *comment
            ];

        //prefix      = repo::seek[qi::lexeme[qi::skip[qi::lit("point")>>qi::lit("[")>>*comment]]];

        start      %= prefix >> *singlePoint;

        //BOOST_SPIRIT_DEBUG_NODES((prefix)(comment)(singlePoint)(start));
    }

  private:
    qi::rule<Iterator, Point(), qi::space_type>              singlePoint;
    qi::rule<Iterator, std::vector<Point>(), qi::space_type> start;
    qi::rule<Iterator, qi::space_type>                       prefix;
    // implicit lexeme:
    qi::rule<Iterator>  comment;
};

#include <nonius/benchmark.h++>
#include <nonius/main.h++>
#include <boost/iostreams/device/mapped_file.hpp>

static boost::iostreams::mapped_file_source src("input.txt");

NONIUS_BENCHMARK("sample", [](nonius::chronometer cm) {
    std::vector<Point> points;

    using It = char const*;
    PointParser<It> g2;

    cm.measure([&](int) {
        It f = src.begin(), l = src.end();
        return phrase_parse(f, l, g2, qi::space, points);
        bool ok =  phrase_parse(f, l, g2, qi::space, points);
        if (ok)
            std::cout << "Parsed " << points.size() << " points\n";
        else
            std::cout << "Parsed failed\n";

        if (f!=l)
            std::cout << "Remaining unparsed input: '" << std::string(f,std::min(f+30, l)) << "'\n";

        assert(ok);
    });
})

图表:

另一个运行输出,实时:

【讨论】:

  • 我已经在此处上传了直播录像(减去前 13 分钟...):livecoding.tv/video/another-spirit-grammar-benchmark-nonius (experiment)
  • 非常感谢您的回复。我在 debug 构建中测量时间。我不确定“预先读取文件”是什么意思。我正在使用 boost::iostreams::mapped_file_source 读取文件并通过 std::string 将数据传递给解析器。除了 singlePoint 之外,我已经剥离了所有规则,但我仍然得到 ~2.5 秒。
  • 我已尝试从 comment 规则中删除船长并按照您的建议简化规则,但解析器不起作用。我在这里做错了什么?
  • 请看我的样品。您可以在直播中看到它有效。我发布了完整的示例和输入是有原因的。
  • 由于某种原因,我的直播无法正常工作。可能是一些网络限制。无论哪种方式,我在 comment 规则中都使用了 qi::char_("a-zA-Z."),因为其中包含逗号 (".") 字符.仅使用 qi::char_ 是行不通的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-17
  • 2011-03-05
  • 2015-01-25
  • 1970-01-01
相关资源
最近更新 更多