RegexOptions.Compiled
nakazuje 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 Regex
obiektu. 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
- Nie przejmujesz się kosztem inicjalizacji obiektu i potrzebujesz dodatkowego zwiększenia wydajności. (uwaga, mówimy tutaj o ułamkach milisekundy)
- 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 Regex
klasy.
Na przykład Regex.Replace
, Regex.Match
etc .. 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.Compiled
opcji 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.