Вступление
"Не используйте регулярки, иначе вместо 1 проблемы, у вас их станет 2!" - как то так говорят знатоки. А что остается делать непослушным, желающим эффективный поиск по большому количеству шаблонов?
Да, для такой довольно специфичной проблемы существуют крутые решения вроде Ragel или re2c. Тем не менее, для своего проекта мне показалось нецелосообразным пока осваивать эти прекрасные технологии.
В этой статье мы рассмотрим альтернативы стандартной библиотеке для регулярных выражений в Go, проведем их бенчмарк по скорости и потребляемой памяти. А также с практической точки зрения рассмотрим различия между ними.
Аналоги
На данный момент можно выделить следующие рабочие альтернативы стандартному regexp, которые можно использовать для нахождения паттернов в Go:
go-re2 - максимально легко заменяет стандартный regexp. Использует C++ re2 для повышения производительности при работе с большими входными данными или сложными выражениями;
regexp2 - многофункциональный движок Regexp для Go. Он не имеет гарантий по времени исполнения, как встроенный пакет regexp, но совместим с Perl5 и .NET;
go-pcre - обеспечивает поддержку Perl-совместимых регулярных выражений с помощью libpcre или libpcre++. Доступна JIT-компиляция, что делает этот форк намного быстрее аналогов. Из минусов - нужно ставить зависимость в виде libpcre3-dev;
rure-go - использует регекс-движкок Rust с помощью привязок CGo. Но есть зависимость от Rust библиотеки, которую необходимо скомплировать;
gohs - regex движок, разработанный с учетом высокой производительности и гибкости. Реализован в виде библиотеки, предоставляющей простой С-API. Тут так же необходимо компилировать и связывать сторонние зависимости;
go-yara - инструмент для помощи в идентификации и классификации образцов вредоносного ПО. Хоть YARA и имеет функционал для шаблонирования и регулярных выражений, но он сильно ограничен, поэтому не буду включать эту библиотеку в тестах далее.
Существующие бенчмарки
Прежде чем начать сравнивать приведенные решения, стоит показать насколько плохо обстоят дела со стандартной regex библиотекой в Go. Я нашел проект, где автор сравнивает быстродействие стандартных regex движков многих ЯП. Суть этого бенчмарка заключается в многократной прогонке 3 регуляркок над предопределенным текстом. Go в этом бенчмарке оказалсяна 3 месте! С конца...
Language | Email(ms) | URI(ms) | IP(ms) | Total(ms) |
|---|---|---|---|---|
Nim Regex | 1.32 | 26.92 | 7.84 | 36.09 |
Nim | 22.70 | 21.49 | 6.75 | 50.94 |
Rust | 26.66 | 25.70 | 5.28 | 57.63 |
... | ... | ... | ... | ... |
Python PyPy3 | 258.78 | 221.89 | 257.35 | 738.03 |
Python 3 | 273.86 | 190.79 | 319.13 | 783.78 |
Go | 248.14 | 241.28 | 360.90 | 850.32 |
C++ STL | 433.09 | 344.74 | 245.66 | 1023.49 |
C# Mono | 2859.05 | 2431.87 | 145.82 | 5436.75 |
Насколько я понял, бенчмаркинг регекс движков не самая простая тема и нужно знать детали имплементаций и алгоритмов для корректного сравнения.
Из других бенчмарков можно выделить следующие:
Benchmarks Game - сравнение движков с оптимизированными версиями под каждый ЯП. Например здесь Go уже не в конце, но и далек от идеала... И конечно же не использует нативную библиотеку, а обертку над PCRE - go-pcre, которую указывал в аналогах.
Performance comparison of regular expression engines - сравнение различных regex движков (PCRE, PCRE-DFA, TRE, Oniguruma, RE2, PCRE-JIT).
A comparison of regex engines - тут автор пытается сравнить движки используя различные регулярки, которые могут представлять сложности в зависимости от имплементации движка. В этом бенчмарке автор в тройку лучших движков выделяет: Hyperscan, PCRE c JIT компиляцией и Rust regex (rure использует его):

Бенчмарк #1
Давайте попробуем теперь сравнить приведенные в аналогах библиотеки с дефолтными regex движками других ЯП. А также посмотрим насколько они будут быстрее стандартного Go regex. Для этого я обновил вышеупомянутый проект, добавив код с новыми библиотеками. Вот что получилось после прогонки на моей машине:
Language | Email(ms) | URI(ms) | IP(ms) | Total(ms) |
|---|---|---|---|---|
Go Rure | 2.61 | 2.11 | 3.33 | 8.05 |
C++ SRELL | 1.74 | 3.03 | 10.67 | 15.45 |
Rust | 11.95 | 0.96 | 3.05 | 15.96 |
Nim | 13.98 | 14.35 | 3.13 | 31.46 |
PHP | 14.43 | 14.63 | 4.87 | 33.93 |
Go PCRE | 14.18 | 14.98 | 6.21 | 35.37 |
C# .Net Core | 10.83 | 5.10 | 26.71 | 42.64 |
Javascript | 42.78 | 30.17 | 0.92 | 73.87 |
Go Re2 | 35.81 | 37.86 | 33.79 | 107.46 |
Go Hyperscan | 90.17 | 31.64 | 8.68 | 130.49 |
Perl | 92.51 | 66.42 | 22.51 | 181.44 |
Dart | 73.48 | 63.60 | 80.52 | 217.59 |
C PCRE2 | 109.73 | 101.05 | 10.95 | 221.72 |
Kotlin | 120.31 | 163.53 | 293.69 | 577.53 |
Java | 205.14 | 201.55 | 295.68 | 702.36 |
Python 3 | 246.26 | 176.01 | 303.08 | 725.34 |
C++ STL | 328.87 | 273.43 | 230.35 | 832.64 |
Go | 270.19 | 275.73 | 504.79 | 1050.71 |
Go Regexp2 | 1703.51 | 1482.60 | 64.46 | 3250.57 |
C# Mono | 2543.82 | 2139.44 | 110.37 | 4793.64 |
Ps. Немного сократил таблицу, поэтому прошу на форк проекта, чтобы посмотреть все результаты.
Pss. В данном случае на результаты могла повлиять виртуализация (тесты проводились в среде Docker на Windows 11).
Тем не менее, видно насколько быстрее могут быть регулярки с некоторыми библиотеками! Мы даже обогнали Rust библиотекой на Go, которая использует библиотеку Rust
