Тестирование текстового фильтра, встроенного в Go, против проверенного и надежного grep

В этой четвертой (и последней) части нашей серии мы рассмотрим сравнение нашего пользовательского средства удаления строк Go с эквивалентной командой в grep. Другие части вы можете найти ниже:

В предыдущих трех сегментах мы создали инструмент в Go, который может удалять совпадающие строки из файла. Затем мы добавили поддержку регулярных выражений и, наконец, сравнили производительность двух подходов с помощью встроенного тестирования производительности Go. Теперь мы хотели бы измерить его производительность по отношению к обратному сопоставлению строк ( -v или --invert-match) в grep.

Мы будем использовать сверхтонкий для тестирования, так как это инструмент с высоким рейтингом, который также доступен через Homebrew для моей машины (MacBook с Apple Silicon). Доступны различные другие инструменты (например, скамейка), которые могут лучше соответствовать вашим потребностям и архитектуре.

Мы будем сравнивать те же три тестовых случая, которые мы рассмотрели в части 3 (описанной ниже), но со значительно большим вводным текстом, чтобы увидеть, как инструменты работают с дополнительной работой.

Установка

Честное сравнение требует хорошей настройки. С этой целью мы будем сравнивать инструмент Go с grep, выполняя три вещи:

  • загрузка файла в grep
  • выполнение обратного сопоставления в grep
  • запись результатов этого обратного совпадения в файл

Мы используем следующую версию grep:

Команды Grep будут иметь следующий формат:

grep "<pattern>" <filepath> > <output filepath>

Команды Lineremover будут иметь следующий формат:

./lineremover -file="path/to/file" -keys="keys if testing substring search" -pattern="pattern if testing regular expression search"

И мы можем запустить тесты как:

hyperfine '<command to benchmark>' --min-runs=10000

Где '<command to benchmark>' будет работать либо grep, либо lineremover, как указано выше. Чтобы обеспечить согласованность и обеспечить достаточное количество данных для экстраполяции, мы будем использовать опцию --min-runs гиперфина, чтобы обеспечить выполнение каждого теста не менее 10 000 раз. Мы также должны знать, что работает на машине, выполняющей тесты — если она замедляется во время одного теста по сравнению с другим, тест может показать это. Имея это в виду, мы будем поддерживать машину в одном и том же состоянии во всех тестах, насколько это возможно, с минимальными фоновыми процессами.

Мы будем тестировать example/inputlong.txt из репозитория lineremover. Этот файл по сути такой же, как example/input.txt, но повторяется 2048 раз, так что время запуска/остановки программы будет меньше влиять на результаты нашего теста по сравнению с фактической работой поиска строки.

Запуск тестов

Эталонное соответствие «привет»

Обратите внимание, что для grep мы сказали Hyperfine игнорировать ошибки ( -i), так как grep выдает ненулевой код выхода в случае отсутствия соответствия (что ожидается для этого теста).

Таким образом, мы видим, что grep в этом тесте немного быстрее — это своего рода ожидаемый результат, учитывая, что он является стандартом в этой области и отсутствует с 1974 года. В lineremovertool мы видим, что сопоставление с образцом также немного медленнее, чем поиск подстроки. Между прогонами также есть небольшая разница.

Эталонное соответствие «большому»

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

Эталонное соответствие «большой», «большой», «яркий», «яркий»

По мере усложнения запроса мы видим, что сопоставление с образцом значительно замедляется по сравнению с поиском подстроки. Здесь мы снова видим, что grep значительно медленнее, чем lineremover, как в режиме сопоставления с образцом, так и в режиме поиска подстроки. Он также имеет гораздо больше дисперсии. И снова неожиданный результат!

Подведение итогов

Сегодня мы изучили механизм сравнения двух инструментов командной строки друг с другом и настроили несколько тестов, которые могли бы разумно представить, как эти инструменты можно использовать в реальном мире. Однако реальная ценность работы при бенчмаркинге заключается в выяснении того, что тестировать, а не в том, как запускать тесты. Какие тесты дадут вам уверенность в том, что какой-то инструмент работает быстрее для вашей рабочей нагрузки или варианта использования? Это самый важный вопрос, на который нужно ответить при разработке теста. Только после этого мы проектируем среду, отвечающую на этот вопрос.

Что касается этого теста, это было интересно! Это был мой первый раз, когда я использовал Hyperfine, так что это довольно круто. В инструменте я ожидал, что сопоставление с образцом будет медленнее, чем поиск подстроки, но не ожидал, что созданный нами инструмент Go превзойдет grep при некоторых условиях поиска. Я пока не уверен, почему grep работал медленнее, но есть предположение, что, возможно, чтение входного файла и запись выходного файла не так оптимизированы, как при использовании буферизованного ввода-вывода в Go? Я также думал, что, возможно, были задействованы некоторые функции ОС (например, кэширование), но я запускал тесты один за другим и в разном порядке и каждый раз получал одинаковые результаты. Если у вас есть идеи, пожалуйста, дайте мне знать!