【问题标题】:parsers written with g++/bison/boost::variant compile very slow用 g++/bison/boost::variant 编写的解析器编译速度很慢
【发布时间】:2016-01-11 14:21:00
【问题描述】:

我用 bison 编写了一个 verilog 解析器,并使用 boost::variant 来存储每个规则的每个变体的所有差异情况。我用一个小例子,BNF 表达式规则来展示我的数据结构:

expression :
  primary
  | expression + expression
primary :
  (expression)
  | number

存储它的数据结构是:

typedef boost::variant<
  std::shared_ptr<exp1>,
  std::shared_ptr<exp2>,
> expression
typedef boost::variant<
  std::shared_ptr<prim1>,
  std::shared_ptr<prim2>,
> primary

exp1/2 和 prim1/2 类用于存储表达式和初级中的两种不同情况:

class exp1 : public BaseClass {
  public :
    std::shared_ptr<primary> mem1;
    exp1(std::shared_ptr<primary> i1):
    mem1(i1)
    {}
}

为简单起见,我只显示exp1,而exp2和prim1/2类似。 在野牛文件中,规则及其动作是这样写的:

expression :
  primary  {
   $$= std::make_shared<expression>(std::make_shared<exp1>($1));
  }

这样的解决方案会导致两个问题:

1 编译速度非常慢,使用 g++ 4.8.4 几乎需要 1 分钟
2 运行时间不是很快

我有一个类似的用ocaml和ocamlyacc编写的解析器,它支持非常优雅的变体规范,1秒编译,运行速度与上面提到的g++版本非常相似。

我使用 boost::variant 的方式有什么问题吗?

==============

我将所有变体更改为带有接受 shared_ptrs 的构造函数的类:

class ComponentBase {
};
Class VariantBase{
};
class prim1;
class prim2;
class exp1;
class exp2;
class expression : public VariantBase {
  expression (shared_ptr<ComponentBase> i1):
    VariantBase(i1) {}
}
class primary : public VariantBase {
  primary (shared_ptr<ComponentBase> i1):
    VariantBase(i1) {}
}

然后编译没有任何改进。看来yacc生成的代码是慢的根源。

有什么建议吗?

【问题讨论】:

  • 我认为语法树比这更复杂。真的,1 分钟太长了?
  • 至于运行时,您必须检查 bison 提供的 cpp 文件。如果源代码难以优化,或者您关闭了优化,您的运行时间将会受到影响。
  • 实际上bison源文件包含3700~行、200~规则和500~组件。对于完整的verilog 2005 语言来说,它是一个大的。但是我有一个类似的用ocaml语言编写的,它的编译时间只有3秒。

标签: c++ c++11 boost boost-variant


【解决方案1】:

更新改为在 Boost Spirit Qi 中添加演示(因为我不精通 flex/bison),请参阅下面的块引用

如果您的 AST 使用共享指针,我建议不必担心运行时性能。

如果您的 AST 使用变体,我建议编译时性能不是问题。 (因此无需担心:))


从概念上讲,shared_ptrs 与变体背道而驰。 Shared_ptrs 通过生命周期管理的节点动态分配促进运行时多态性。

变体通过自动存储持续时间促进静态多态性。

Go 运行时多态性

如果您对运行时多态 AST 节点(在 AST 上的转换中通常非常方便)感到满意,那么我建议您不要使用变体。相反,使它们成为同一节点层次结构的一部分。

粗略的草图:


Go 静态多态

删除运行时多态性(最好是变量头)将减少编译时间。当需要制作、内联和优化许多模板实例组合时,编译时间会增加。

更新

这演示了删除共享指针和运行时多态性以获得更简单的 AST。

上述模板实例化+内联的爆炸式解释说明“老式”Qi 实现的编译速度会很慢,可能比您的原始代码慢。 X3版本没有这个问题。

【讨论】:

  • 你的画似乎建议我打破表达式和主要定义的递归?但这种递归是必要的,因为要解析的语言需要这种递归。
  • 一个更严重的问题是,不仅bison生成的parser.cpp编译速度慢,其他所有包含变体定义头文件的文件编译速度也很慢。
  • 老实说,我认为您表示 AST 的方式不必与产品相匹配。这是我更了解的:boost spirit x3。编译速度相当快,只是值语义;为不同的运算符准备,包括解析和打印 AST。
  • 这是在 c++11c++03 上编译的 Qi 版本。 警告:Qi 版本对编译器的负担很重,因此可能需要更长的时间 来编译。当然,有点 pImpl-idiom 在头文件中摆脱了它。
  • 您的示例在同一个 c++ 文件中指定扫描仪和解析器似乎非常优雅。但是我发现spirit是针对LL语言的,不兼容yacc中的LR或者LALR使用,对吧?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多