RegexOptions.Compiledindica al motore di espressione regolare di compilare l'espressione di espressione regolare in IL utilizzando la generazione di codice leggero ( LCG ). Questa compilation avviene durante la costruzione dell'oggetto e lo rallenta pesantemente . A loro volta, le partite che usano l'espressione regolare sono più veloci.
Se non si specifica questo flag, l'espressione regolare viene considerata "interpretata".
Prendi questo esempio:
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]);
});
}
}
Esegue 4 test su 3 diverse espressioni regolari. Innanzitutto verifica una singola partita una tantum (compilata vs non compilata). In secondo luogo, verifica le corrispondenze ripetute che riutilizzano la stessa espressione regolare.
I risultati sul mio computer (compilato in versione, nessun debugger allegato)
1000 partite singole (costruisci Regex, Match e dispose)
Digita | Piattaforma | Numero di prova | Controllo e-mail semplice | Ext Email Check
-------------------------------------------------- ----------------------------
Interpretato | x86 | 4 ms | 26 ms | 31 ms
Interpretato | x64 | 5 ms | 29 ms | 35 ms
Compilato | x86 | 913 ms | 3775 ms | 4487 ms
Compilato | x64 | 3300 ms | 21985 ms | 22793 ms
1.000.000 di corrispondenze: riutilizzo dell'oggetto Regex
Digita | Piattaforma | Numero di prova | Controllo e-mail semplice | Ext Email Check
-------------------------------------------------- ----------------------------
Interpretato | x86 | 422 ms | 461 ms | 2122 ms
Interpretato | x64 | 436 ms | 463 ms | 2167 ms
Compilato | x86 | 279 ms | 166 ms | 1268 ms
Compilato | x64 | 281 ms | 176 ms | 1180 ms
Questi risultati mostrano che le espressioni regolari compilate possono essere fino al 60% più veloci nei casi in cui riutilizzi l' Regexoggetto. Tuttavia, in alcuni casi, può essere più lento di costruire oltre 3 ordini di grandezza .
Mostra anche che la versione x64 di .NET può essere da 5 a 6 volte più lenta quando si tratta di compilare espressioni regolari.
La raccomandazione sarebbe quella di utilizzare la versione compilata nei casi in cui entrambi
- Non ti interessa il costo di inizializzazione degli oggetti e hai bisogno di aumentare le prestazioni extra. (nota che stiamo parlando di frazioni di millisecondi qui)
- Ti preoccupi un po 'del costo di inizializzazione, ma stai riutilizzando così tante volte l'oggetto Regex che lo compenserà durante il ciclo di vita dell'applicazione.
Spanner in lavorazione, la cache Regex
Il motore delle espressioni regolari contiene una cache LRU che contiene le ultime 15 espressioni regolari che sono state testate utilizzando i metodi statici sulla Regexclasse.
Ad esempio: Regex.Replace, Regex.Matchecc .. tutto l'uso della cache Regex.
La dimensione della cache può essere aumentata impostando Regex.CacheSize. Accetta modifiche di dimensioni in qualsiasi momento durante il ciclo di vita dell'applicazione.
Le nuove espressioni regolari vengono memorizzate nella cache solo dagli helper statici nella classe Regex. Se costruisci i tuoi oggetti, la cache viene controllata (per riutilizzarli e eliminarli), tuttavia, l'espressione regolare che costruisci non viene aggiunta alla cache .
Questa cache è una cache LRU banale , viene implementata utilizzando un semplice elenco a doppio collegamento. Se ti capita di aumentarlo a 5000 e utilizzare 5000 chiamate diverse sugli helper statici, ogni costruzione di espressioni regolari eseguirà la scansione delle 5000 voci per vedere se è stata precedentemente memorizzata nella cache. C'è un lucchetto attorno al controllo, quindi il controllo può ridurre il parallelismo e introdurre il blocco del thread.
Il numero è impostato piuttosto basso per proteggersi da casi come questo, anche se in alcuni casi potresti non avere altra scelta che aumentarlo.
La mia forte raccomandazione non sarebbe mai passare l' RegexOptions.Compiledopzione a un assistente statico.
Per esempio:
\\ WARNING: bad code
Regex.IsMatch("10000", @"\\d+", RegexOptions.Compiled)
Il motivo è che stai rischiando pesantemente di perdere una cache LRU che attiverà una compilazione super costosa . Inoltre, non hai idea di cosa stiano facendo le librerie da cui dipendi, quindi hai poca capacità di controllare o prevedere la migliore dimensione possibile della cache.
Vedi anche: blog del team BCL
Nota : questo è rilevante per .NET 2.0 e .NET 4.0. Ci sono alcune modifiche previste in 4.5 che potrebbero causare la revisione di questo.