Jak działa RegexOptions.Compiled?


169

Co dzieje się za kulisami, gdy oznaczysz wyrażenie regularne jako do kompilacji? Czym to się różni / różni od wyrażenia regularnego w pamięci podręcznej?

Korzystając z tych informacji, jak określić, kiedy koszt obliczeń jest znikomy w porównaniu ze wzrostem wydajności?


dobre źródło najlepszych praktyk Regex: docs.microsoft.com/en-us/dotnet/standard/base-types/ ...
CAD facet

Odpowiedzi:


302

RegexOptions.Compilednakazuje aparatowi wyrażeń regularnych skompilowanie wyrażenia regularnego do IL przy użyciu lekkiego generowania kodu ( LCG ). Ta kompilacja ma miejsce podczas budowy obiektu i mocno go spowalnia. Z kolei dopasowania przy użyciu wyrażenia regularnego są szybsze.

Jeśli nie określisz tej flagi, wyrażenie regularne będzie traktowane jako „zinterpretowane”.

Weź ten przykład:

public static void TimeAction(string description, int times, Action func)
{
    // warmup
    func();

    var watch = new Stopwatch();
    watch.Start();
    for (int i = 0; i < times; i++)
    {
        func();
    }
    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}

static void Main(string[] args)
{
    var simple = "^\\d+$";
    var medium = @"^((to|from)\W)?(?<url>http://[\w\.:]+)/questions/(?<questionId>\d+)(/(\w|-)*)?(/(?<answerId>\d+))?";
    var complex = @"^(([^<>()[\]\\.,;:\s@""]+"
      + @"(\.[^<>()[\]\\.,;:\s@""]+)*)|("".+""))@"
      + @"((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
      + @"\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+"
      + @"[a-zA-Z]{2,}))$";


    string[] numbers = new string[] {"1","two", "8378373", "38737", "3873783z"};
    string[] emails = new string[] { "sam@sam.com", "sss@s", "sjg@ddd.com.au.au", "onelongemail@oneverylongemail.com" };

    foreach (var item in new[] {
        new {Pattern = simple, Matches = numbers, Name = "Simple number match"},
        new {Pattern = medium, Matches = emails, Name = "Simple email match"},
        new {Pattern = complex, Matches = emails, Name = "Complex email match"}
    })
    {
        int i = 0;
        Regex regex;

        TimeAction(item.Name + " interpreted uncached single match (x1000)", 1000, () =>
        {
            regex = new Regex(item.Pattern);
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

        i = 0;
        TimeAction(item.Name + " compiled uncached single match (x1000)", 1000, () =>
        {
            regex = new Regex(item.Pattern, RegexOptions.Compiled);
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

        regex = new Regex(item.Pattern);
        i = 0;
        TimeAction(item.Name + " prepared interpreted match (x1000000)", 1000000, () =>
        {
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

        regex = new Regex(item.Pattern, RegexOptions.Compiled);
        i = 0;
        TimeAction(item.Name + " prepared compiled match (x1000000)", 1000000, () =>
        {
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

    }
}

Wykonuje 4 testy na 3 różnych wyrażeniach regularnych. Najpierw testuje pojedyncze dopasowanie jednorazowe (skompilowane vs nieskompilowane). Po drugie, testuje powtórzone dopasowania, które wykorzystują to samo wyrażenie regularne.

Wyniki na moim komputerze (skompilowane w wersji, bez dołączonego debugera)

1000 pojedynczych dopasowań (skonstruuj regex, dopasuj i usuń)

Wpisz | Platforma | Numer trywialny | Proste sprawdzanie poczty | Ext Email Check
-------------------------------------------------- ----------------------------
Zinterpretowany | x86 | 4 ms | 26 ms | 31 ms
Zinterpretowany | x64 | 5 ms | 29 ms | 35 ms
Skompilowane | x86 | 913 ms | 3775 ms | 4487 ms
Skompilowane | x64 | 3300 ms | 21985 ms | 22793 ms

1,000,000 dopasowań - ponowne użycie obiektu Regex

Wpisz | Platforma | Numer trywialny | Proste sprawdzanie poczty | Ext Email Check
-------------------------------------------------- ----------------------------
Zinterpretowany | x86 | 422 ms | 461 ms | 2122 ms
Zinterpretowany | x64 | 436 ms | 463 ms | 2167 ms
Skompilowane | x86 | 279 ms | 166 ms | 1268 ms
Skompilowane | x64 | 281 ms | 176 ms | 1180 ms

Te wyniki pokazują, że skompilowane wyrażenia regularne mogą być nawet o 60% szybsze w przypadkach, gdy ponownie używasz Regexobiektu. Jednak w niektórych przypadkach może być o ponad 3 rzędy wielkości wolniejsze do skonstruowania.

Pokazuje również, że wersja .NET x64 może być od 5 do 6 razy wolniejsza, jeśli chodzi o kompilację wyrażeń regularnych.


Zaleca się użycie wersji skompilowanej w przypadkach, gdy albo

  1. Nie przejmujesz się kosztem inicjalizacji obiektu i potrzebujesz dodatkowego zwiększenia wydajności. (uwaga, mówimy tutaj o ułamkach milisekundy)
  2. Trochę przejmujesz się kosztem inicjalizacji, ale używasz obiektu Regex tyle razy, że skompensuje to podczas cyklu życia aplikacji.

Klucz w przygotowaniu, pamięć podręczna Regex

Silnik wyrażeń regularnych zawiera pamięć podręczną LRU, która przechowuje 15 ostatnich wyrażeń regularnych, które zostały przetestowane przy użyciu statycznych metod Regexklasy.

Na przykład Regex.Replace, Regex.Matchetc .. wszystko użycie cache Regex.

Rozmiar pamięci podręcznej można zwiększyć, ustawiając Regex.CacheSize. Akceptuje zmiany rozmiaru w dowolnym momencie podczas cyklu życia aplikacji.

Nowe wyrażenia regularne są buforowane tylko przez pomocników statycznych w klasie Regex. Jeśli konstruujesz obiekty, pamięć podręczna jest sprawdzana (pod kątem ponownego użycia i zderzenia), jednak utworzone wyrażenie regularne nie jest dołączane do pamięci podręcznej .

Ta pamięć podręczna jest trywialną pamięcią podręczną LRU, jest zaimplementowana za pomocą prostej podwójnie połączonej listy. Jeśli zdarzy się, że zwiększysz ją do 5000 i użyjesz 5000 różnych wywołań pomocników statycznych, każda konstrukcja wyrażenia regularnego będzie przeszukiwać 5000 wpisów, aby sprawdzić, czy została wcześniej zapisana w pamięci podręcznej. Wokół czeku znajduje się blokada , więc kontrola może zmniejszyć równoległość i wprowadzić blokowanie gwintu.

Ta liczba jest ustawiona dość nisko, aby uchronić się przed takimi przypadkami, chociaż w niektórych przypadkach możesz nie mieć innego wyjścia, jak ją zwiększyć.

Moim stanowczym zaleceniem nigdy nie byłoby przekazanie RegexOptions.Compiledopcji statycznemu pomocnikowi.

Na przykład:

\\ WARNING: bad code
Regex.IsMatch("10000", @"\\d+", RegexOptions.Compiled)

Powodem jest to, że bardzo ryzykujesz przegapienie pamięci podręcznej LRU, co spowoduje bardzo kosztowną kompilację. Ponadto nie masz pojęcia, na czym polegasz biblioteki, więc masz niewielkie możliwości kontrolowania lub przewidywania najlepszego możliwego rozmiaru pamięci podręcznej.

Zobacz też: blog zespołu BCL


Uwaga : dotyczy to .NET 2.0 i .NET 4.0. Istnieją pewne oczekiwane zmiany w 4.5, które mogą spowodować, że zostanie to zmienione.


11
Świetna odpowiedź. Do własnych celów często używam Compiledw kodzie strony internetowej, w której faktycznie przechowuję obiekt statyczny (obejmujący całą aplikację) Regex. Tak więc Regexjedyny musi być zbudowany raz, gdy IIS uruchamia aplikację, a następnie jest ponownie używany tysiące razy. Działa to dobrze, o ile aplikacja nie restartuje się często.
Steve Wortham

W00! Te informacje pomogły mi przyspieszyć proces z 8-13 godzin do ~ 30 minut. Dziękuję Ci!
Robert Christ,

3
Świetna odpowiedź Sam, czy możesz zaktualizować informacje o tym, co się zmieniło w wersji> 4.5? (Wiem, że zmieniłeś swój stack jakiś czas temu ...)
gdoron wspiera Monikę,

@gdoronissupportingMonica Wprowadzono pewne ulepszenia wydajności Regex w NET 5.0. Widziałem wpis na blogu dotyczący tego. Możesz to sprawdzić tutaj
kapozade

42

Ten wpis na blogu zespołu BCL daje ładny przegląd: „ Wydajność wyrażeń regularnych ”.

Krótko mówiąc, istnieją trzy typy wyrażeń regularnych (każdy jest wykonywany szybciej niż poprzedni):

  1. interpretowane

    szybkie tworzenie w locie, wolne wykonywanie

  2. skompilowany (ten, o który wydajesz się pytać)

    wolniejszy do tworzenia w locie, szybki do wykonania (dobry do wykonywania w pętlach)

  3. wstępnie skompilowane

    tworzenie w czasie kompilacji aplikacji (bez kary za tworzenie w czasie wykonywania), szybkie wykonanie

Tak więc, jeśli zamierzasz wykonać to wyrażenie regularne tylko raz lub w sekcji aplikacji, która nie ma krytycznego znaczenia dla wydajności (tj. Sprawdzanie poprawności danych wejściowych użytkownika), możesz wybrać opcję 1.

Jeśli zamierzasz uruchomić wyrażenie regularne w pętli (tj. Parsowanie pliku linia po linii), powinieneś skorzystać z opcji 2.

Jeśli masz wiele wyrażeń regularnych, które nigdy się nie zmienią w Twojej aplikacji i są używane intensywnie, możesz wybrać opcję 3.


1
Numer 3 może być ułatwiony dzięki niestandardowemu RoslynowiCompileModule . Cholera, muszę dokładniej przyjrzeć się nowej platformie.
Christian Gollhardt

9

Należy zauważyć, że wydajność wyrażeń regularnych od .NET 2.0 została ulepszona dzięki pamięci podręcznej MRU nieskompilowanych wyrażeń regularnych. Kod biblioteki Regex nie reinterpretuje już tego samego nieskompilowanego wyrażenia regularnego za każdym razem.

Więc nie jest potencjalnie większa wydajność kara z skompilowany i na bieżąco wyrażenia regularnego. Oprócz wolniejszych czasów ładowania system wykorzystuje również więcej pamięci do kompilowania wyrażeń regularnych do instrukcji.

Zasadniczo obecna rada to albo nie kompiluj wyrażeń regularnych, albo kompiluj je wcześniej do oddzielnego zestawu.

Ref: BCL Team Blog Wydajność wyrażeń regularnych [David Gutierrez]



0

Mam nadzieję, że poniższy kod pomoże ci zrozumieć koncepcję funkcji re.compile

import re

x="""101 COM    Computers
205 MAT   Mathematics
189 ENG   English
222 SCI Science
333 TA  Tamil
5555 KA  Kannada
6666  TL  Telugu
777777 FR French
"""

#compile reg expression / successfully compiled regex can be used in any regex 
#functions    
find_subject_code=re.compile("\d+",re.M)
#using compiled regex in regex function way - 1
out=find_subject_code.findall(x)
print(out)
#using compiled regex in regex function way - 2
out=re.findall(find_numbers,x)
print(out)

#few more eg:
#find subject name
find_subjectnames=re.compile("(\w+$)",re.M) 
out=find_subjectnames.findall(x)
print(out)


#find subject SHORT name
find_subject_short_names=re.compile("[A-Z]{2,3}",re.M) 
out=find_subject_short_names.findall(x)
print(out)

Dziękuję za odpowiedź, ale Twój kod jest w języku Python . Pytanie dotyczyło opcji RegexOptions.Compiled w Microsoft .NET framework . Możesz zobaczyć tag [ .net ] dołączony pod pytaniem.
stomia

OOO tak! Dzięki
stomii
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.