ANYKS
EN
в разработкечасть AWH · 51 160 строк

awh::regex и Grok

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

01 Коротко

Быстрее PCRE2 почти везде, а на «Эльбрусе» в разы

37 из 41сценария, где наш машинный код быстрее машинного кода PCRE2; медиана доли 1,18
0 из 45сценариев, где наш толкователь уступает толкователю PCRE2; медиана доли 1,46
×6,6на «Эльбрусе»: наш код против PCRE2, у которой кода под e2k нет
307встроенных шаблонов Grok
02 Устройство

Пять способов приложить выражение к тексту

Движок выбирает способ по свойствам выражения и текста, а не держит один на все случаи.

СпособКогдаЧто делает
Prefilterдо всякого автоматанабор допустимых начальных байтов и обязательный литерал: участки текста пропускаются без запуска автомата
DFAнужен ответ «есть или нет»состояния строятся по мере надобности, один проход по тексту
Pikeнужны границы группвсе состояния разом, захваты за линейное время — без взрыва на подобранном выражении
Backtrackвне регулярного подмножестваобратные ссылки, просмотр вперёд и назад, атомарные группы, рекурсия
CodegenARM64, x86-64 и e2kпрограмма выражения превращается в машинный код сопоставителя

Собранное выражение после сборки не изменяется и разделяется потоками без замков; рабочее состояние сопоставления у каждого потока своё.

03 Порождение кода

Перемещаемый код — решение, принятое до первой строки

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

Участок памяти никогда не бывает записываемым и исполняемым разом; на Apple ARM64 — через MAP_JIT, на OpenBSD — с метками BTI.

Рычаг кода над нашим же толкователем с каждой правкой толкователя убывает, и это мера успеха: прежде он доходил до ×153, ныне наибольший — ×34,7. Толкователь догнал код там, где отставал от него в десятки раз.

ВыражениеРычаг кода
\w+(?=@)×34,7
(\w+)@(\w+)\.(\w+)×32,6
(?>\w+)@\w+×28,0
.*?needle×14,0
Content-Lengthведёт отбор
04 Замеры

Сорок пять сценариев, две меры

Сопоставлений в секунду; ARM64 (Apple M4 Max), обе стороны собраны с -O3. Сличаются порознь две пары: наш машинный код против машинного кода PCRE2 и наш толкователь против её толкователя. Смешивать их нельзя: прежде единственная мера — код против кода — скрывала отставание толкователя, и вскрылось оно лишь на «Эльбрусе».

Машинный код против машинного кодаAWHPCRE2Доля
.*needle113 3148 93512,68
(?>\w+)@\w+9 086 4322 429 4203,74
(?:[a-z]* ?)*dog42 179 84111 491 4793,67
alpha|bravo|charlie|delta|echo|foxtrot11 4056 2011,84
^(GET|POST) (\S+) HTTP/(\d)\.(\d)$105 297 79559 989 5131,76
(\w+) \17 014 3644 372 8191,60
\((?:[^()]|(?R))*\)28 721 84925 997 9301,10
(?:HT|TP)/171 259 96584 772 7030,84
Толкователь против толкователяAWHPCRE2Доля
(?:fox|dog)trap212 3902 62980,77
\bneedle\b210 04610 32920,33
.*needle73 0765 10414,32
Content-Length122 768 37924 091 3065,10
\((?:[^()]|(?R))*\)2 409 0101 652 5451,46
(?:[a-z]+/)+v116 252 67322 992 9130,71

Машинным кодом: быстрее PCRE2 в 37 сценариях из 41, из четырёх оставшихся три — 0,99, заметно уступаем лишь в (?:HT|TP)/1; медиана доли 1,18. Толкователем: впереди в 21 сценарии из 45, вровень в 24, позади — ни в одном; медиана доли 1,46. «Впереди» — доля от 1,5, «позади» — ниже 0,67. Сборка выражения у нас в 2,3 раза дороже (0,44 от PCRE2): строятся две программы — прямая и обратная — и выражение разбирается глубже; собирается оно однажды, прикладывается миллионы раз.

05 Эльбрус

Машинный код под «Эльбрус», которого нет у PCRE2

Порождатель обучен набору команд e2k (Эльбрус-8С2, lcc): 23 метода посредника, кодировщик сверен с ассемблером стенда байт в байт. У PCRE2 порождение под эту архитектуру невозможно вовсе, поэтому здесь наш код сличается с её толкователем.

ВыражениеНаш кодPCRE2Доля
(?:fox|dog)trot3 75310934,4
\w+(?=@)132 2495 76522,9
(?>\w+)@\w+210 07412 33017,0
(\w+)@(\w+)\.(\w+)17 2231 03016,7
.*needle3 00721613,9
[0-9]{3,5}1 032 2211 131 0700,91

Итог: впереди в 39 сценариях из 41, медиана доли 6,6. Толкователи между собой: впереди в 22, вровень в 23, позади — ни в одном.

06 Хранилище

Собранные выражения — без повторного разбора

Образ памяти

Программа выражения пишется как есть, восстановление — установка обзора на участок записи без размещения и копирования.

Недоверие к записи

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

Шифрование и срок

Шифрование и сжатие обработчиками потребителя, срок годности записи, опознание машины и набора команд.

07 Правильность

Двадцать стендов сверки и одно намеренное расхождение

СтендСличенийРасхождений
свойства Юникода, 1 225 свойств1 362 278 4000
сокращённые классы символов26 689 5360
границы совпадения на наборе выражений112 2610
вердикт и обратный проход236 4980
приём и отклонение выражений200 000 шаблонов0
графемные кластеры48 218 62469 131

Графемы разбиваются по действующей редакции UAX #29, а таблица PCRE2 отстаёт от неё в трёх правилах. Два отдельных стенда показывают, что расхождение ограничено этими правилами: у обоих ноль расхождений на тех же 48 млн сличений.

08 Grok

Разбор журналов именованными шаблонами

Реестр шаблонов, разворот ссылок вида %{ИМЯ:поле:вид} в регулярное выражение и извлечение полей в JSON.

grok.build("%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{INT:code:int}");

// 192.168.1.10 GET /api/v1/orders?id=17 200
{"client":"192.168.1.10","method":"GET","request":"/api/v1/orders?id=17","code":200}
  • Ссылки разворачиваются текстом, а не вызовом подшаблона: сборщику открыто всё дерево, и работают и отбор начальных байтов, и порождение кода.
  • Названия полей шире имён групп — дефис, точка, повторы: %{IP:src-ip}:%{INT:src.port}.
  • Число — только по объявленному виду. Номер версии и код с ведущим нулём остаются текстом, а вывод — правильным JSON.
  • Ошибки — отказом сборки: несуществующий шаблон и зацикленные ссылки не проходят молча.

Разбор по шаблонам Grok есть и в онлайн-конвертере ACU: строку журнала можно превратить в JSON, XML или YAML прямо в браузере.