Теперь про скорость. Действительно ли ruff такой быстрый?
На том же проекте на 500к строк кода (это много) на моей машине ванильный flake8 заканчивает работу примерно за 6.2 секунды, потребляя при этом 700% cpu. То есть он попытался отожрать почти все ядра. Вывод линтера я отправляю в /dev/null, чтобы измерять именно работу линтера, а не время печатания простыни ошибок в stdout. Перезапускаешь линтер — и снова те же самые 6.2 секунды, потому что во flake8 нет никакого кэширования, он каждый раз делает всё заново.
Запуск ruff занимает 0,42 секунды и потребляет примерно 500% cpu. Оп, в 15 раз быстрее, проца использует меньше. Второй запуск ruff занимает уже всего 0,1 секунды, потому что он берёт все результаты из кэша. Оп, в 62 раза быстрее. Если поменять один файл, то ruff пересчитает этот один файл, а все остальные результаты всё равно возьмёт из кэша, и суммарное время будет те же 0,1 секунды.
А давайте теперь добавим несколько плагинов. Голый линтер нам же не особо интересен, да? Устанавливаю flake8-bugbear, flake8-logging-format, flake8-pytest-style. Количество ошибок от flake8 вырастает до 5443, а время работы до 9.4 секунды. Включаю эти же 3 набора правил в ruff. Количество ошибок 5648 (чёт разница увеличилась, странно). Время работы на холодную колеблется в интервале 0,4-0,6 секунд, а с прогретым кэшем отрабатывает стабильно за всё те же 0,1 секунды. Здесь разница по скорости уже почти в 100 раз, и это я добавил относительно лёгкие плагины. Добавляем ещё несколько плагинов, и flake8 уже работает дольше 20 секунд, а ruff примерно как и раньше. Вообще, скорость работы flake8 довольно быстро деградирует.
Почему он быстрее? Ну, во-первых, потому что написан на низкоуровневом языке, без жирных объектов в памяти и без сборки мусора. Во-вторых, не делает лишней работы — все проверки выполняются за один проход. Ровно одно чтение каждого файла, ровно один разбор AST/CST каждого файла, и на этом работают вообще все проверки внутри тулзы (и не только проверки, читай дальше). В противовес, модульному flake8 зачастую приходится делать эту же работу несколько раз, потому что некоторым проверкам недостаточно просто AST дерева, им приходится самостоятельно читать и разбирать файлы, чтобы вычленить больше информации. В-третьих, здесь сразу сделали кэш. Блин, это же очевидная оптимизация. Почему во flake8 этого до сих пор нет?
Мелочь ли это? Ну хз. Я за 20 секунд успеваю заскучать и переключиться в телегу, что практически гарантировано выключит меня из состояния потока. И уж точно я буду стараться запускать этот медленный линтер как можно реже, а значит буду получать меньше обратной связи. С другой стороны, ruff можно запускать хоть на каждую новую строчку кода. У него даже есть режим --watch, когда он висит в фоне и перезапускает проверки на каждое изменение.
Теперь про скорость. Действительно ли ruff такой быстрый?
На том же проекте на 500к строк кода (это много) на моей машине ванильный flake8 заканчивает работу примерно за 6.2 секунды, потребляя при этом 700% cpu. То есть он попытался отожрать почти все ядра. Вывод линтера я отправляю в /dev/null, чтобы измерять именно работу линтера, а не время печатания простыни ошибок в stdout. Перезапускаешь линтер — и снова те же самые 6.2 секунды, потому что во flake8 нет никакого кэширования, он каждый раз делает всё заново.
Запуск ruff занимает 0,42 секунды и потребляет примерно 500% cpu. Оп, в 15 раз быстрее, проца использует меньше. Второй запуск ruff занимает уже всего 0,1 секунды, потому что он берёт все результаты из кэша. Оп, в 62 раза быстрее. Если поменять один файл, то ruff пересчитает этот один файл, а все остальные результаты всё равно возьмёт из кэша, и суммарное время будет те же 0,1 секунды.
А давайте теперь добавим несколько плагинов. Голый линтер нам же не особо интересен, да? Устанавливаю flake8-bugbear, flake8-logging-format, flake8-pytest-style. Количество ошибок от flake8 вырастает до 5443, а время работы до 9.4 секунды. Включаю эти же 3 набора правил в ruff. Количество ошибок 5648 (чёт разница увеличилась, странно). Время работы на холодную колеблется в интервале 0,4-0,6 секунд, а с прогретым кэшем отрабатывает стабильно за всё те же 0,1 секунды. Здесь разница по скорости уже почти в 100 раз, и это я добавил относительно лёгкие плагины. Добавляем ещё несколько плагинов, и flake8 уже работает дольше 20 секунд, а ruff примерно как и раньше. Вообще, скорость работы flake8 довольно быстро деградирует.
Почему он быстрее? Ну, во-первых, потому что написан на низкоуровневом языке, без жирных объектов в памяти и без сборки мусора. Во-вторых, не делает лишней работы — все проверки выполняются за один проход. Ровно одно чтение каждого файла, ровно один разбор AST/CST каждого файла, и на этом работают вообще все проверки внутри тулзы (и не только проверки, читай дальше). В противовес, модульному flake8 зачастую приходится делать эту же работу несколько раз, потому что некоторым проверкам недостаточно просто AST дерева, им приходится самостоятельно читать и разбирать файлы, чтобы вычленить больше информации. В-третьих, здесь сразу сделали кэш. Блин, это же очевидная оптимизация. Почему во flake8 этого до сих пор нет?
Мелочь ли это? Ну хз. Я за 20 секунд успеваю заскучать и переключиться в телегу, что практически гарантировано выключит меня из состояния потока. И уж точно я буду стараться запускать этот медленный линтер как можно реже, а значит буду получать меньше обратной связи. С другой стороны, ruff можно запускать хоть на каждую новую строчку кода. У него даже есть режим --watch, когда он висит в фоне и перезапускает проверки на каждое изменение.