Производительность .NET Regex

В настоящее время я использую следующее регулярное выражение для удаления первого элемента беседы из почтового элемента Outlook HTML:

.*?>(([^:]+?):<.*?\2):

Чтобы удалить первый элемент беседы, я просто заменяю первое вхождение группы 1 значением группы 2. В .NET это будет выглядеть примерно так:

private static readonly Regex LAST_CONVERSATION_REPLACE_PATTERN = new Regex(@".*?>(([^:]+?):<.*?\2):", RegexOptions.Compiled);
// ...
MatchCollection matches = LAST_CONVERSATION_REPLACE_PATTERN.Matches(htmlMessageBody);
if (matches.Count > 0)
{
    Match match = matches[0];
    if (match.Groups.Count > 2)
    {
        return htmlMessageBody.ReplaceFirst(match.Groups[1].ToString(), match.Groups[2].ToString());
    }
}

ReplaceFirst — это мой собственный метод расширения строки. Тем не менее, производительность этого немного разочаровывает. В то время как Regex Coach может применить это к огромным почтовым элементам за микросекунду, в надстройке Outlook это занимает до 10 секунд.

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

.*?>(From:<.*?From):

Используя это регулярное выражение, мне требуется меньше секунды, чтобы получить то, что я хочу. Однако это будет зависеть от языка, и поэтому я бы предпочел предыдущий вариант. Есть ли способ ускорить работу в .NET? Или есть сторонняя библиотека регулярных выражений, которая могла бы работать лучше?

Спасибо за любые предложения и с наилучшими пожеланиями

Паскаль


person Pascal Kesseli    schedule 15.07.2012    source источник
comment
Пример электронного письма можно найти здесь: tempfiles.net/download /201207/253263/ Я намерен удалить промежуточный элемент беседы из Джейн Доу (включая информацию заголовка и тело).   -  person Pascal Kesseli    schedule 19.07.2012
comment
Obtw., поскольку меня интересует только первое совпадение, я применил этот обходной путь: Match match = LAST_CONVERSATION_REPLACE_PATTERN.Match(htmlMessageBody); if (match.Success) { //... Так что теперь это просто вопрос любопытства :-) .   -  person Pascal Kesseli    schedule 19.07.2012


Ответы (2)


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

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

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

.*?>(((?>[^:]+)):<.*?\b\2):
person Tim Pietzcker    schedule 15.07.2012
comment
.NET не поддерживает притяжательные квантификаторы, но вместо этого вы можете использовать атомарную группу. - person Alan Moore; 16.07.2012
comment
@AlanMoore: Ой, я забыл об этом. Спасибо! - person Tim Pietzcker; 16.07.2012
comment
Спасибо за ваши предложения. К сожалению, лень никак не влияет на производительность. Удалил все три - никакого эффекта. Между прочим, ни один из них не добавил слово «граница». - person Pascal Kesseli; 19.07.2012

Я уверен, что это .*? вызывает вашу проблему, они оба. Например, первый заставляет регулярное выражение останавливаться и пытаться сопоставить каждую позицию с начала документа. К счастью для вас, вам все равно не нужна эта часть. Но вам действительно нужно быть более конкретным, когда это возможно, а не полагаться все время на .*?.

Попробуй это:

private static readonly Regex LAST_CONVERSATION_REPLACE_PATTERN 
    = new Regex(@"^(?>(\w+:).*)(?>\s+(?!^\1).*)+", RegexOptions.Multiline);

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

^(?>(\w+:).*) соответствует чему-то похожему на имя заголовка в начале строки, а .* занимает остальную часть этой строки. Помещение его в атомарную группу гарантирует, что, если попытка сопоставления в начале строки потерпит неудачу в более поздней части регулярного выражения, он не будет возвращаться, чтобы попробовать другие способы сопоставления строки.

(?>\s+(?!^\1).*) использует разделитель строк и следующую строку, но только после того, как просмотр вперед подтвердит, что он не начинается с имени целевого заголовка.

person Alan Moore    schedule 16.07.2012
comment
Спасибо за отзыв! К сожалению, я не думаю, что мы можем предположить, что заголовок начинается с новой строки, так как это HTMLBody. Чуть позже выложу пример. - person Pascal Kesseli; 19.07.2012