"Если компьютеры станут слишком мощными, мы можем организовать их в комитеты. Это их прикончит" (с) неизвестный автор

Большие гонки regexp
Большие гонки regexp

Введение

Так или иначе сталкиваться с регулярными выражениями приходилось большинству разработчиков. Мое первое знакомство произошло с реализацией regexp в STL std::regexp. Чаще всего регулярки используются в проверке входных данных, что-то вроде проверки корректности введенного пользователем URL, адреса IPv4, адреса IPv6, телефонного номера и при этом скорость выполнения операции regex не сильно влияет на время отклика от приложения. Но, что если вам приходится проверять сотни, тысячи или даже десятки тысяч правил и все это на постоянно меняющихся наборах входных данных в реальном времени? В этой ситуации вам не просто нужен быстрый алгоритм, вам понадобится лучший из них, вам понадобится чемпион!

Выбираем участников соревнований

Критерии отбора участников довольно просты:

  • наличие исходных кодов

  • совместимость по самим правилам, хотя бы на базовом уровне

  • код написан на С/С++

Hidden text

Да, я фанат С++, поэтому regexp на языках отличных от C/C++ не рассматриваю, сорян

Итак, погуглив пояндексив немного находим следующих претендентов:

Присвоенное имя

Язык

Ссылка на репозиторий

stdlib

C++

-

tiny-regex-c

C

https://github.com/kokke/tiny-regex-c

ximtech

C

https://github.com/ximtech/Regex

boost

C++

https://github.com/boostorg/regex.git

hyperscan

С

https://github.com/intel/hyperscan

google-re2

C++

https://github.com/google/re2

Методология

Теперь нужно определиться как будем проверять участников.

  1. Есть заранее составленный список регулярных выражений одинаковый для всех участников.

  2. Есть заранее составленный список наборов данных одинаковый для всех участников.

  3. Для выравнивания замеров времени будем итеративно проверять каждое (из NR) правило IR раз на ID итерациях наборов данных (из ND), т.е. количество проверок для каждого участника будет: SUM = IR * NR * ID * ND

  4. Для снижения взаимного влияния участников друг на друга будем собирать отдельные приложения для каждого участника.

  5. Для сохранения актуальности проекта, исходный код участника клонируется из его репозитория, из ветки master или толерантного main.

  6. Если участник - это библиотека, то проводится его сборка.

  7. Непосредственно код который осуществляет проверку состоит из класса c двумя абстрактными методами - один для подготовки правил (в том числи для их компиляции) и один для непосредственной проверки скомпилированных правил на наборе данных.

  8. Запускать гонки будем на виртуальной машине в облаке

Большая гонка

Код представлен в репозитории https://github.com/shvit/regexp_checker

Для желающих самостоятельно провести гонку на своем компьютере, потребуется установить зависимости. Для Ubuntu 20.04 (подходит также и для ubuntu-latest):

sudo apt install build-essential cmake ragel git libboost-all-dev

git clone https://github.com/abseil/abseil-cpp abseil-cpp && cd abseil-cpp && mkdir -p bin && cd bin && cmake -DCMAKE_CXX_STANDARD=17 -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. && cmake --build . && sudo cmake --install .

теперь клонируем гонку себе на комп и запускаем

git clone https://github.com/shvit/regexp_checker
cd regexp_checker
make check

Проще, конечно же, воспользоваться готовым результатом запуска на виртуалке в GitHub Actions:

Участник

Время

Место

boost

4.914s

2

google-re2

16.012s

3

hyperscan

0.826s

1

stdlib

65.517s

6

tiny-regex-c

23.288s

4

ximtech

51.421s

5

В качестве проверки, вместо юнит тестов - все участники выдали одинаковое количество совпадений - по 2'800'000.

Проверки проводились с параметрами: NR=10, IR=20'000, ND=5, ID=10. Общее количество проверок для одного участника SUM=10'000'000.

Заключение

При текущем составе участников бесспорным чемпионом является проект hyperscan, но также порадовал участник занявший второе место - boost.

Также необходимо отметить, что проекты tiny-regex-c и ximtech поддерживают сокращенный набор элементов regexp, из-за чего пришлось существенно сократить список правил. Проект ximtech является развитием проекта tiny-regex-c. Также в проекте tiny-regex-c есть баги и также он не позволяет компилировать более одного правила из-за своей архитектуры.

Для использования проекта google-re2 потребуется установка библиотек Abseil и также потребуется не тривиальная линковка готового проекта.

В заключение, хочется отметить наличие у проекта hyperscan возможности применения правил на потоках (streams), что в некоторых случаях может быть решающим.

Если кто-то знает других потенциальных участников гонки - предлагайте в комментариях.

Комментарии (26)


  1. Tomas245
    07.05.2024 15:39
    +2

    Неплохая статья, я ещё слышал про ctre https://github.com/hanickadot/compile-time-regular-expressions, может также себя неплохо покажет)


    1. sh_vit Автор
      07.05.2024 15:39
      +2

      Спасибо, за наводку!


  1. Wesha
    07.05.2024 15:39
    +2

    проверки корректности введенного пользователем URL, адреса IPv4, адреса IPv6, телефонного номера и

    (расслабляясь и выдыхая) Ну хоть кто-то не считает, что он может регулярками проверить емейл-адреса.


    1. sh_vit Автор
      07.05.2024 15:39
      +2

      стреляный воробей )))


  1. jpegqs
    07.05.2024 15:39
    +5

    А где pcre2? Там JIT компилятор есть.


    1. sh_vit Автор
      07.05.2024 15:39
      +3

      не нагуглилось наяндексилось... спасибо на наводку!

      явно второй этап вырисовывается )))


  1. BorisU
    07.05.2024 15:39

    А где pcre?


    1. sh_vit Автор
      07.05.2024 15:39

      не нагуглилось наяндексилось...
      был бы признателен за ссылку на репу


      1. BorisU
        07.05.2024 15:39

        ну там выше ссылка на pcre2 есть, я когда отвечал еще не видел того коммента. не думаю, что есть смысл смотреть на старые версии того же самого.


  1. cranium256
    07.05.2024 15:39
    +6

    Ещё бы в тестировании хорошо учесть насколько библиотеки умеют в регулярки. А то "совместимость по самим правилам, хотя бы на базовом уровне" и "[...] поддерживают сокращенный набор элементов regexp" выглядит несколько расплывчато. Есть общепринятые "стандарты" языка регулярок: например Basic (BRE), Extended (ERE), Perl-совместимый (PCRE). Хорошо бы сравнивать не только скорость, но и реализованные в библиотеке фичи самих регулярок. Такой обзор был бы более полезен. Какой толк с очень быстрой библиотеки, которая реализует урезанный базовый уровень? А кому-то в своём приложении может и такого уровня хватит.

    И ещё, какие лицензии у тестируемых библиотек? Это тоже немаловажно, если использовать в других проектах.


    1. sh_vit Автор
      07.05.2024 15:39

      Спасибо за отзыв! Наверно, продолжение надо сделать с учетом общепринятых "стандартов", т.е. либо без примитивных реализаций, либо с разделением на лиги (любительская/профессиональная), ну, и с учетом вновь заявленных участников соревнований.
      Про лицензии думал уже, надо будет добавить.


  1. R0bur
    07.05.2024 15:39

    Странно, что между boost и stdlib такой разрыв, потому что «The Boost Regex library provides regular expression support for C++, this library is the ancestor to std::regex and still goes beyond and offers some advantages to, the standard version.».


    1. sh_vit Автор
      07.05.2024 15:39

      Согласен. Это повод поразбираться.
      Хотя, чисто формально, это в порядке вещей, ибо boost не стоит на месте.


      1. OMR_Kiruha
        07.05.2024 15:39

        А каким компилятором и соответственно стандартной библиотекой пользовались во время теста? Может есть смысл сравнить большую тройку компиляторов?

        P.S. Нашёл в логах что GCC 11.4.0


        1. sh_vit Автор
          07.05.2024 15:39
          +1

          Формально, при сборке тестовых приложений, компилятор жестко не задан - используется переменная CXX, но по факту, при сборке на виртуалке в GitHub Actions, используется gcc из пакета build-essential из ubuntu-latest.
          В соревнованиях главное обеспечить равные условия, хотя зависимость от компилятора была бы интересна.


    1. lorc
      07.05.2024 15:39
      +1

      AFAIK, стандарт описывает интерфейс, но не описывает имплементацию. Соответственно в gcc libc++, clang libc++ и msvc libc++, например, могут быть совершенно разные по скорости движки для регулярных выражений. То же самое касается и других алгоритмов, например алгоритмов сортировки. Для них, стандарт, вроде бы описывает только ограничение по O большому и все.


  1. sergio_nsk
    07.05.2024 15:39
    +5

    cmake -DCMAKE_CXX_STANDARD=17 -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. && cmake --build

    Верно я понимаю, что по умолчанию собираем и запускаем конфигурацию Debug без оптимизаций компилятора?


    1. wl2776
      07.05.2024 15:39

      Нет, это конфигурация с пустым cmake_build_type, ключи компилятора могут отличаться и от debug, и от release

      Правильно было бы в явном виде указать Release.


      1. sh_vit Автор
        07.05.2024 15:39
        +2

        Согласен, надо явно добавить релиз. Спасибо за комментарий!


  1. sunnybear
    07.05.2024 15:39

    Судя по коду, в hyperscan как раз и выполняется компиляция регулярок, поэтому повторные прогоны должны быть быстрыми - его имеет смысл сравнивать только с jit


    1. sh_vit Автор
      07.05.2024 15:39
      +1

      Добавление участника pcre2 - в планах. Спасибо за комментарий!
      Если есть на примете другие - предлагайте.


  1. Razoomnick
    07.05.2024 15:39

    Проверки проводились с параметрами: NR=10, IR=20'000, ND=5, ID=10. Общее количество проверок для одного участника SUM=10'000'000

    Меня смущает IR=20'000. Скажем, если в библиотеке реализовано кэширование на каком-либо уровне, она получит значительное преимущество.

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

    Я не считаю такие оптимизации "нечестным преимуществом", кэширование и компиляция - отличные подходы. Но методология тестирования не учитывает, что скорость сопоставления строк с регулярными выражениями может драматически меняться в зависимости от того, повторяются ли регулярные выражения в процессе выполнения теста.


    1. voldemar_d
      07.05.2024 15:39

      Драматически - это от английского dramatically?


      1. Razoomnick
        07.05.2024 15:39

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


  1. galaxy
    07.05.2024 15:39

    Как эти библиотеки справляются с патологическими регулярками типа (a?)^na^n? (пример для n=5 — /a?a?a?a?a?aaaaa/ ~ "aaaaa")

    Смысл существования re2 как раз в отсутствии патологий бектрекинга (https://swtch.com/~rsc/regexp/regexp1.html)


  1. JordanCpp
    07.05.2024 15:39

    Почему stdlib настолько медленная? И за счёт чего достигается скорость?