Подтвердить что ты не робот

Почему мое регулярное выражение намного медленнее компилируется, чем интерпретируется?

У меня есть большое и сложное регулярное выражение С#, которое работает нормально при интерпретации, но немного медленнее. Я пытаюсь ускорить это, установив RegexOptions.Compiled, и это, кажется, занимает около 30 секунд в первый раз и сразу после этого. Я пытаюсь это отрицать, сначала компилируя регулярное выражение на сборку, поэтому мое приложение может быть как можно быстрее.

Моя проблема в том, когда происходит компиляция, независимо от того, скомпилирована ли она в приложении:

Regex myComplexRegex = new Regex(regexText, RegexOptions.Compiled);
MatchCollection matches = myComplexRegex.Matches(searchText);
foreach (Match match in matches) // <--- when the one-time long delay kicks in
{

} 

или с помощью команды Regex.CompileToAssembly:

MatchCollection matches = new CompiledAssembly.ComplexRegex().Matches(searchText);
foreach (Match match in matches) // <--- when the one-time long delay kicks in
{

} 

Это делает сборку на сборке практически бесполезной, так как я все еще получаю задержку при первом вызове foreach. Я хочу, чтобы вся компиляция была выполнена во время компиляции (при вызове Regex.CompileToAssembly), а не во время выполнения. Где я ошибаюсь?

(Код, который я использую для компиляции в сборку, похож на http://www.dijksterhuis.org/regular-expressions-advanced/, если это актуально).

Edit:

Должен ли я использовать new при вызове скомпилированной сборки в new CompiledAssembly.ComplexRegex().Matches(searchText);? Он дает ошибку "ссылка на объект" без него.

Обновление 2

Спасибо за ответы/комментарии. Регулярное выражение, которое я использую, довольно длинное, но в основном прямолинейное, список тысяч слов, каждый из которых разделен символом |. Я не вижу, чтобы это было проблемой возврата. Строка subject может быть длиной всего одна буква, и она все равно может привести к задержке компиляции. Для регулярного выражения RegexOptions.Compiled это займет более 10 секунд, чтобы выполнить, когда регулярное выражение содержит 5000 слов. Для сравнения, не скомпилированная версия регулярного выражения может принимать 30 000 слов и выполнять их практически мгновенно.

После проведения большого количества тестов на этом, я думаю, что я узнал:

  • Не используйте RegexOptions.Compiled, когда ваше регулярное выражение имеет много альтернатив - его можно очень медленно компилировать.
  • .Net будет использовать ленивую оценку для regex, когда это возможно, и AFAI может видеть, что это расширяет (по крайней мере до некоторой степени) компиляцию регулярных выражений, Регулярное выражение будет полностью скомпилировано только тогда, когда оно должно быть, и, похоже, не существует способа форсировать компиляцию раньше времени.
  • Regex.CompileToAssembly была бы намного более полезна, если бы регулярные выражения могли быть полностью скомпилированы, кажется, что они не имеют смысла, поскольку они являются бессмысленными.

Пожалуйста, поправьте меня, если я ошибаюсь или что-то не хватает!

4b9b3361

Ответ 1

При использовании RegexOptions.Compiled вы должны обязательно повторно использовать объект Regex. Кажется, вы не делаете этого.

RegexOptions.Compiled является компромиссом. Начальная конструкция Regex будет медленнее, потому что код компилируется "на лету", но каждый матч должен быть быстрее. Если ваше регулярное выражение изменяется во время выполнения, вероятно, не будет пользы от использования RegexOptions.Compiled, хотя это может зависеть от фактического выражения.

Обновление в соответствии с комментариями

Если ваш фактический код похож на тот, который вы опубликовали, вы не пользуетесь преимуществами CompileToAssembly, поскольку вы создаете новые, на лету скомпилированные экземпляры Regex каждый раз, когда выполняется этот фрагмент кода. Чтобы воспользоваться преимуществами CompileToAssembly, вам нужно сначала скомпилировать Regex; затем возьмите сгенерированную сборку и укажите ее в своем проекте. Затем вы должны создать экземпляр генерируемых, сильно типизированных типов выражений.

В примере, к которому вы ссылаетесь, он имеет регулярное выражение с именем FindTCPIP, которое скомпилируется в тип с именем FindCTPIP. Когда это необходимо использовать, нужно создать новый экземпляр этого конкретного типа, например:

TheRegularExpressions.FindTCPIP MatchTCP = new TheRegularExpressions.FindTCPIP();

Ответ 2

Попробуйте использовать Regex.CompileToAssembly, а затем ссылку на сборку, чтобы вы могли создавать объекты Regex. RegexOptions.Compiled - это параметр времени выполнения, регулярное выражение все равно будет перекомпилировано каждый раз при запуске приложения.

Ответ 3

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

Можете ли вы разместить регулярное выражение и образец ввода там, где он медленный.

Лично мне не нужно было компилировать регулярное выражение, хотя было интересно увидеть некоторые фактические цифры о производительности, если вы взяли этот путь.

Ответ 4

Чтобы принудительно инициализировать, вы можете вызвать Match против пустой строки. Кроме того, вы можете использовать ngen для создания собственного образа выражения, чтобы ускорить процесс еще больше. Но, вероятно, самое главное, это по сути так же быстро, как и 30 000 строк .IndexOf или string.Contains или Regex.Match для данного текста, чем компиляция ginormous big expression для сопоставления с одним текстом. Поскольку для этого требуется намного меньше компиляции, джитсинга и т.д., Поскольку машина состояний намного проще.

Еще одна вещь, которую вы могли бы рассмотреть, - это токенизировать текст и пересечь его со списком слов, которые вы после.

Ответ 5

После тщательного тестирования, я могу подтвердить, что подозрения на миккель, по сути, правильны. Даже при использовании Regex.CompileToAssembly() и статической привязки результирующей DLL к приложению существенная первоначальная задержка в первом практическом совпадении (по крайней мере, для шаблонов, включающих множество альтернатив ORed). Более того, начальная задержка при первом совпадении зависит от того, с каким текстом вы сталкиваетесь. Например, сопоставление с пустой строкой или каким-либо другим произвольным текстом приведет к меньшей начальной задержке, но позже вы получите дополнительные задержки, когда в новом тексте будут встречены фактические положительные совпадения. Единственный способ полностью гарантировать будущие совпадения - все это будет молниеносно - изначально принудительно поддерживать положительный результат во время выполнения с текстом, который действительно соответствует. Конечно, это дает максимальную начальную задержку (в обмен на то, что все будущие матчи будут молниеносно).

Я углубился, чтобы понять это лучше. Для каждого регулярного выражения, скомпилированного в сборку, триплет классов записывается со следующим шаблоном имен: { RegexName, RegexNameFactoryN, RegexNameRunnerN }. Ссылка на RegexNameFactoryN класс инициализируется в момент RegexName CTOR, но RegexNameRunnerN класс не является. См. runnerref поля factory и runnerref в базовом классе Regex. runnerref - кэшированная слабая ссылка на объект RegexNameRunnerN. После различных экспериментов с отражением я могу подтвердить, что все три из этих скомпилированных классов быстры, и RegexNameFactoryN.CreateInstance() (которая возвращает исходную ссылку RegexNameRunnerN) также быстро. Начальная задержка происходит где-то внутри RegexRunner.Scan() или дерева вызова, и, следовательно, она, вероятно, вне досягаемости скомпилированного MSIL, сгенерированного Regex.CompileToAssembly() поскольку это дерево вызовов включает в себя множество не абстрактных функций. Это очень неудачно и означает, что преимущества производительности процесса компиляции С# Regex только расширяются до сих пор: во время выполнения всегда будет какая-то существенная задержка в первый раз, когда встречается положительное совпадение (по крайней мере для этого класса шаблонов с множеством ORED).

Я предполагаю, что это связано с тем, как движок недетерминированных конечных автоматов (NFA) выполняет некоторые собственные внутренние кэширования/экземпляры во время выполнения при обработке шаблона.

jessehouwing предложение ngen интересно и может улучшить производительность. Я его не тестировал.