Я смотрю на портирование грамматики JavaScript, которая использует среду выполнения C ++ из ANTLR3 в ANTLR4 (технически версия ANTLR3 использует среду выполнения C, но сейчас это не актуально).
Основная проблема, с которой я борюсь, это производительность. У меня есть несколько проблем с производительностью, которые мешают мне даже приблизиться к нулю, но я собираюсь сосредоточиться на наиболее серьезной проблеме в этой теме: кажется невероятно трудным создать полностью кэшированный DFA и анализировать файлы, которые попали в цель. некэшированные переходы невероятно медленные.
Я создал небольшой набор тестов из 16 файлов и разделил этапы прогрева и тестирования. (Antlr3 на самом деле не нуждается в разминке, но что угодно)
Когда прогрев установлен === тестовый набор (16 файлов каждый)
Test ms
------------------
Antlr3Warmup 338
Antlr4Warmup 55860
Antlr3Test 335
Antlr4Test 439
Выглядит близко, верно? Теперь давайте посмотрим, что произойдет, если наборы не пересекаются (8 файлов каждый)
Test ms
------------------
Antlr3Warmup 78
Antlr4Warmup 32032
Antlr3Test 273
Antlr4Test 24356
Очевидно, что за 32 секунды он даже не приблизился к созданию DFA — мы просто собрали достаточно, чтобы иметь хорошую производительность для идентичных файлов. Я мог бы попытаться добавить больше файлов в пакет разминки, но производительность, кажется, никогда не улучшается, и требуется более 15 м, чтобы пройти больший пакет разминки (например, jquery + angular + реагировать). Выполнение профилирования показывает, что узкое место действительно ParserATNSimulator::computeTargetState
,
Что происходит? Есть ли более умный способ создать полный кэш DFA? Например. Parser::buildCompleteDFA()
,
Для некоторого контекста я начал с https://github.com/antlr/grammars-v4/blob/master/javascript/JavaScriptParser.g4 и переработал правило singleExpression, чтобы сделать всю грамматику SLL (до этого производительность казалась еще хуже).
Изменить: Иван предположил, что виноват левый рекурсивный рефакторинг. Итак, вот те же тесты без рефакторинга (теперь грамматика — LL):
Когда прогрев установлен === тестовый набор (16 файлов каждый)
Benchmark ms
--------------------
Antlr3Warmup 337
Antlr4Warmup 4224
Antlr3Test 311
Antlr4Test 1785
ANTLR4 потерял преимущество на полностью кэшированных файлах, но производительность на не кэшированных файлах улучшилась. Поможет ли это в долгосрочной перспективе после создания кеша DFA?
Benchmark ms (same as before) ms (extra warmup)
--------------------
Antlr3Warmup 77 77
Antlr4Warmup 455 47649
Antlr3Test 275 274
Antlr4Test 3750 3559
Не так много. Могу поспорить, что более продолжительные прогревания улучшат производительность, но эта цифра 1785 года из предыдущего теста является нижней границей.
Изменить 2: Вот некоторые данные профилирования о том, почему нижняя граница так высока:
Time spent w/ ATN transition logic: 12%
Time spent accessing the DFA: 16%
Time spent w/ shared_ptr: 27%
Time spent w/ dynamic_casts: 33%
(это уже после того, как я уже удалил некоторые dynamic_casts. На самом деле все они являются downcast, но я думаю, что LLVM не может оптимизировать это хорошо)
Кроме того, я до сих пор вижу звонки computeReachSet
даже для одного файла, запускаемого дважды! В грамматике SLL парсер adaptivePredict
бы только позвонить getExistingTargetState
Задача ещё не решена.
Других решений пока нет …