Laramago: как я убирал лишнюю работу из анализатора

Содержание
  1. Куда уходило время
  2. Сначала отсечь то, что точно не подходит
  3. Общий обход и компактный кеш
  4. Что получилось на полном приложении
  5. Идея, которую пришлось убрать
  6. Что мне понравилось в этой работе

Статический анализ удобен, когда его можно запустить после правки и быстро вернуться к работе. С Laramago сначала получалось иначе: проверка реального Laravel-приложения занимала несколько минут. Поэтому после добавления возможностей занялся скоростью. Самые полезные изменения нашлись в повторных обходах исходников и устройстве кеша.

Оптимизация Laramago: повторные пути разбора кода объединяются в общий обход и компактный кеш
Меньше повторных обходов и лишних данных в кеше. Сгенерированная иллюстрация.

Куда уходило время

Laramago помогает анализатору Mago понимать Laravel: свойства моделей Eloquent, связи, коллекции и другие привычные конструкции фреймворка. Для этого расширению приходится разбираться в исходниках и собирать дополнительные сведения о коде.

Проблема оказалась в том, сколько раз выполнялась эта работа. Одной проверке нужны аргументы вызова, другой — свойства модели, третьей — информация о соседних конструкциях. Каждая задача выглядит небольшой, но вместе они снова и снова приводят к одним файлам.

Профилирование показало ещё одну неприятную связь. Деревья разобранного кода занимают много места. При последовательных обходах ограниченный кеш постоянно вытеснял данные, которые вскоре требовались следующей проверке. Приходилось заново разбирать исходники и восстанавливать структуры. Кеш был, но характер обращений мешал ему помогать.

После этого стало понятнее, за что браться: уменьшать повторную работу и объём данных, которые нужно держать в памяти.

Сначала отсечь то, что точно не подходит

Первое полезное изменение — раньше отвечать на простой вопрос: может ли выражение вообще подходить под нужное правило?

Если проверка ищет определённую форму обращения к переменной или свойству, иногда уже по форме выражения видно, что это другой случай. Тогда дорогой разбор можно не запускать. Для неоднозначных конструкций остаётся полный путь проверки.

На одном из ранних этапов такой отбор уменьшил число обращений к чтению исходников у трёх выбранных обработчиков примерно на 43%. В отдельной серии полных запусков медиана времени снизилась с 308,84 до 236,68 секунды — примерно на 23%.

Это хороший пример небольшой правки с заметным эффектом. Ускоряется одна часто вызываемая операция, и за полный проход экономия складывается из тысяч таких решений.

Общий обход и компактный кеш

Следующий шаг затронул устройство анализа. Нескольким проверкам нужны сведения из одного исходника, поэтому часть обхода теперь выполняется совместно. Вместо повторного путешествия по дереву каждая получает подготовленные данные для чтения.

Заодно изменил то, что сохраняется между обращениями. Для некоторых задач достаточно знать вид конструкции, её свойства и расположение в файле. Хранить ради этого полное дерево разбора слишком дорого. В оптимизированных участках используются более компактные сведения, а служебная история хешей исходников ограничена.

Отдельная история — восстановление после вытеснения из кеша. Раньше запрос одного результата мог снова запускать соседние проверки. Теперь пересчитывается только то, за чем пришёл вызывающий код. На большом приложении это важно: вытеснение происходит в обычной работе, а за ним не должен тянуться лишний повторный обход.

Эти изменения вошли в Laramago 0.1.7. Проверка актуальности исходников при этом сохраняется: после изменения файла следующий анализ должен увидеть новый код.

Что получилось на полном приложении

В итоговой Linux-серии последний набор изменений сравнивался с предыдущим уже оптимизированным вариантом. На каждую конфигурацию пришлось по восемь измеряемых запусков. Получились такие медианы:

Вариант LaramagoВремя анализаПиковая память
Предыдущий шаг оптимизации64,31 с1358,17 МиБ
Итоговый вариант в 0.1.761,48 с1298,22 МиБ

Последний шаг дал ещё около 4,4% по времени и столько же по пиковому потреблению памяти. Память здесь учитывается для всей группы процессов вместе с файловым кешем. Замеры выполнялись с двумя CPU и общим лимитом 6 ГиБ.

Раннее сокращение с 309 до 237 секунд и эта таблица относятся к разным сериям: между ними менялись снимок приложения и настройки. Каждую конкретную правку я сравнивал с её исходным вариантом в одной серии.

На нативном Windows итоговый анализ занял около 143 секунд. Это отдельная среда со своим поведением процессов и файлового кеша. Поэтому результат около минуты относится к описанному Linux-запуску.

При оптимизации сверял полный результат анализа. В итоговой серии сохранились все 13 100 диагностических записей, включая содержимое сообщений и повторения. Так было видно, что выигрыш получен за счёт организации работы.

Идея, которую пришлось убрать

Не всё, что выглядело полезным для кеша, ускоряло приложение. Пробовал сжатие: хотелось уместить больше разобранных данных в том же объёме памяти. Но упаковка и распаковка добавили затрат, и полный анализ стал медленнее. Этот вариант убрал.

После такой попытки особенно хорошо понимаешь ценность полного запуска. У отдельного механизма может улучшиться показатель, а у всей программы — вырасти время выполнения. Поэтому память, процессорное время и обычное время ожидания смотрел вместе.

Что мне понравилось в этой работе

Самая интересная часть оптимизации — разобраться, зачем программа делает каждое действие. Почему один файл читается снова? Кому действительно нужно целое дерево? Что должно восстановиться после вытеснения из кеша?

Ответы помогли яснее разделить общую подготовку данных и отдельные проверки. У каждой проверки осталась своя задача, а повторяющаяся работа постепенно собралась в общие механизмы.

Для повседневной разработки это вполне ощутимый результат. Чем меньше времени занимает проверка, тем проще запускать её после небольших изменений. Именно таким инструментом я и хочу сделать Laramago: чтобы анализ помогал работать с Laravel-кодом и вписывался в обычный рабочий ритм.

Есть что добавить?

Ваш email не публикуется. Обязательные поля отмечены *