我正在编写一个编译器,我想提高它的性能。我发现大约50%的时间花在解析源文件上。由于源文件非常小,之后我做了很多转换,在我看来它是可以完善的。
我的解析器是一个带有词法分析器(带有lexer :: pos_iterator)的Boost Spirit解析器,我有一个中等大小的语法。我正在将源解析为AST。
我的问题是我不知道解析过程中花费的时间最多:AST节点,词法分析器,解析器规则或内存的副本。
我不认为这是I / O问题,因为我正在使用SSD并且我在开始时完全读取文件然后仅使用内存版本。
我尝试使用分析器,但是花费时间的方法是Boost的一些方法,其名称长度为数百个字符,我不确切知道它们的作用......
那么,是否有一种首选方法来对Boost Spirit Parser及其语法进行基准测试? 或者是否有一些规则可用于验证某些特定点的效率?
由于
感兴趣的人的来源:
答案 0 :(得分:12)
我已经快速扫描了一些东西。
我的探查器很快告诉我,构造语法和(特别是)词法分析器对象需要相当多的资源。
实际上,只需更改SpiritParser.cpp 中的一行就可以节省40%的执行时间 1 (〜28s降至~17s):
lexer::Lexer lexer;
进入
static const lexer::Lexer lexer;
现在,
使语法静态涉及使其无状态。
我做到了这一点position_begin
移至qi::_a
(使用qi::locals
)和在适当的时间将其作为继承属性传递
EDDIGrammar
和ValueGrammar
语法,例如
start %= qi::eps [ qi::_a = qi::_r1 ] >> program;
以及ValueGrammar
中正在使用外部的个别规则。
这有许多次优的副作用:
lexer::pos_iterator_type
没有默认输出流重载 qi::position(position_begin)
表达式已被“伪造”,并且有一个相当复杂的替代品:
auto local_pos = qi::lazy (
boost::phoenix::construct<qi::position>(qi::_a)
);
这似乎并不理想。 (理想情况下,有人希望用修改过的自定义解析器指令替换qi::position
,该指令知道如何从qi :: locals(?)获取begin_position,这样就不需要调用了解析器表达式懒惰?)
无论如何,implementing these further changes2 削减了约15%的执行时间:
static const lexer::Lexer lexer;
static const parser::EddiGrammar grammar(lexer);
try {
bool r = spirit::lex::tokenize_and_parse(
position_begin, position_end,
lexer,
grammar(boost::phoenix::cref(position_begin)),
program);
缺乏想法:
Position::file
和Position::theLine
的替代方案?复制字符串似乎比必要的重。我更愿意存储const char *
。您还可以查看Boost Flyweight qi::position
指令中是否真的需要预先跳过?希望这有帮助。
[1] 在test/cases/*.eddi
中解析所有测试用例100次like so (github)时:
for (int i=0; i<100; i++)
for (auto& fname : argv)
{
eddic::ast::SourceFile program;
std::cout << fname << ": " << std::boolalpha << parser.parse(fname, program, nullptr) << "\n";
}
用简单的
计时time ./test ../../test/cases/*.eddi | md5sum
md5sum
充当理智检查。
[2] 我在这里创建了一个带有概念验证重构的拉取请求https://github.com/wichtounet/eddic/pull/52