Я работаю над компилятором, и я хотел бы улучшить его производительность. Я обнаружил, что около 50% времени уходит на разбор исходных файлов. Поскольку исходный файл довольно мал, и после этого я довольно много преобразований, мне кажется, что он совершенен.
Мой анализатор — это анализатор Boost Spirit с лексером (с lexer :: pos_iterator), и у меня есть грамматика среднего размера. Я анализирую источник в AST.
Моя проблема в том, что я понятия не имею, что занимает больше всего времени при разборе: копии узлов AST, лексера, правил синтаксического анализатора или памяти.
Я не думаю, что это проблема ввода-вывода, так как я работаю на SSD и что я читаю файл полностью в начале, а затем использую только версию памяти.
Я пытался использовать профилировщики, но методы, которые требуют времени, — это некоторые методы Boost с именами длиной в сотни символов, и я не знаю точно, что они делают …
Итак, есть ли предпочтительный способ сравнить анализатор Boost Spirit и его грамматику?
Или есть какие-то правила, которые можно использовать для проверки эффективности в определенных точках?
Спасибо
Источники для заинтересованных:
Я дал вещи быстрого сканирования.
Мой профилировщик быстро сказал мне, что для создания грамматики и (особенно) объекта lexer потребовалось немало ресурсов.
Действительно, просто изменив одну строку в SpiritParser.cpp сэкономил 40% времени выполнения1 (~ 28 секунд до ~ 17 секунд):
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 (?), поэтому нет необходимости вызывать выражение парсера лениво?)
В любом случае, реализуя эти дальнейшие изменения2 сбрил еще ~ 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 Flyweightqi::position
Директива?Надеюсь это поможет.
[1] При разборе всех тестовых случаев в test/cases/*.eddi
100 раз вот так (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
Других решений пока нет …