Domanda

Questa è solo una domanda per soddisfare la mia curiosità. Ma per me è interessante.

Ho scritto questo piccolo benchmark semplice. Chiama 3 varianti dell'esecuzione di Regexp in un ordine casuale alcune migliaia di volte:

Fondamentalmente, uso lo stesso modello ma in modi diversi.

  1. Il tuo modo ordinario senza RegexOptions . A partire da .NET 2.0 questi non vengono memorizzati nella cache. Ma dovrebbe essere " memorizzato nella cache " perché è contenuto in un ambito piuttosto globale e non ripristinato.

  2. Con RegexOptions.Compiled

  3. Con una chiamata al Regex.Match statico (modello, input) che viene memorizzato nella cache in .NET 2.0

Ecco il codice:

static List<string> Strings = new List<string>();        
static string pattern = ".*_([0-9]+)\\.([^\\.])
    Not compiled and not automatically cached:
    Total milliseconds: 6185,2704
    Adjusted milliseconds: 6185,2704

    Compiled and not automatically cached:
    Total milliseconds: 2562,2519
    Adjusted milliseconds: 2551,56949184038

    Not compiled and automatically cached:
    Total milliseconds: 2378,823
    Adjusted milliseconds: 2336,3187176891
quot;; static Regex Rex = new Regex(pattern); static Regex RexCompiled = new Regex(pattern, RegexOptions.Compiled); static Random Rand = new Random(123); static Stopwatch S1 = new Stopwatch(); static Stopwatch S2 = new Stopwatch(); static Stopwatch S3 = new Stopwatch(); static void Main() { int k = 0; int c = 0; int c1 = 0; int c2 = 0; int c3 = 0; for (int i = 0; i < 50; i++) { Strings.Add("file_" + Rand.Next().ToString() + ".ext"); } int m = 10000; for (int j = 0; j < m; j++) { c = Rand.Next(1, 4); if (c == 1) { c1++; k = 0; S1.Start(); foreach (var item in Strings) { var m1 = Rex.Match(item); if (m1.Success) { k++; }; } S1.Stop(); } else if (c == 2) { c2++; k = 0; S2.Start(); foreach (var item in Strings) { var m2 = RexCompiled.Match(item); if (m2.Success) { k++; }; } S2.Stop(); } else if (c == 3) { c3++; k = 0; S3.Start(); foreach (var item in Strings) { var m3 = Regex.Match(item, pattern); if (m3.Success) { k++; }; } S3.Stop(); } } Console.WriteLine("c: {0}", c1); Console.WriteLine("Total milliseconds: " + (S1.Elapsed.TotalMilliseconds).ToString()); Console.WriteLine("Adjusted milliseconds: " + (S1.Elapsed.TotalMilliseconds).ToString()); Console.WriteLine("c: {0}", c2); Console.WriteLine("Total milliseconds: " + (S2.Elapsed.TotalMilliseconds).ToString()); Console.WriteLine("Adjusted milliseconds: " + (S2.Elapsed.TotalMilliseconds*((float)c2/(float)c1)).ToString()); Console.WriteLine("c: {0}", c3); Console.WriteLine("Total milliseconds: " + (S3.Elapsed.TotalMilliseconds).ToString()); Console.WriteLine("Adjusted milliseconds: " + (S3.Elapsed.TotalMilliseconds*((float)c3/(float)c1)).ToString()); }

Ogni volta che lo chiamo il risultato è sulla falsariga di:

    Not compiled and not automatically cached:
    Total milliseconds: 6456,5711
    Adjusted milliseconds: 6456,5711

    Compiled and not automatically cached:
    Total milliseconds: 2668,9028
    Adjusted milliseconds: 2657,77574842168

    Not compiled and automatically cached:
    Total milliseconds: 6637,5472
    Adjusted milliseconds: 6518,94897724836

Quindi il gioco è fatto. Non molto, ma circa il 7-8% di differenza.

Non è l'unico mistero. Non posso spiegare perché il primo modo sarebbe molto più lento perché non viene mai rivalutato ma tenuto in una variabile statica globale.

A proposito, questo è su .Net 3.5 e Mono 2.2 che si comportano esattamente allo stesso modo. Su Windows.

Quindi, qualche idea, perché la variante compilata potrebbe persino restare indietro?

Edit1:

Dopo aver corretto il codice, i risultati ora appaiono così:

<*>

Il che praticamente oscura anche tutte le altre domande.

Grazie per le risposte.

È stato utile?

Soluzione

Nella versione Regex.Match stai cercando l'input nel pattern. Prova a scambiare i parametri in giro.

var m3 = Regex.Match(pattern, item); // Wrong
var m3 = Regex.Match(item, pattern); // Correct

Altri suggerimenti

L'ho notato simile comportamento. Mi chiedevo anche perché la versione compilata sarebbe più lenta, ma ho notato che sopra un certo numero di chiamate, la versione compilata è più veloce. Così ho scavato in Reflector e ho notato che per un Regex compilato, c'è ancora una piccola configurazione che viene eseguita alla prima chiamata (in particolare, creando un'istanza dell'appropriato RegexRunner oggetto).

Nel mio test, ho scoperto che se ho spostato sia la funzione di costruzione che una prima chiamata a eliminazione diretta sulla regex al di fuori del timer, la regex compilata ha vinto indipendentemente da quante iterazioni ho eseguito.


Per inciso, la memorizzazione nella cache che il framework sta eseguendo quando si utilizzano metodi Regex statici è un'ottimizzazione che è necessaria solo quando si utilizzano metodi Regex statici. Questo perché ogni chiamata a un metodo Regex statico crea un nuovo oggetto Regex . Nel costruttore della classe Regex deve analizzare il modello. La memorizzazione nella cache consente alle chiamate successive di metodi Regex statici di riutilizzare il RegexTree analizzato dalla prima chiamata, evitando così la fase di analisi.

Quando usi i metodi di istanza su un singolo oggetto Regex , questo non è un problema. L'analisi viene comunque eseguita solo una volta (quando si crea l'oggetto). Inoltre, puoi evitare di eseguire tutto l'altro codice nel costruttore, nonché l'allocazione dell'heap (e la successiva garbage collection).

Martin Brown notato che hai notato ha invertito gli argomenti con la tua chiamata statica Regex (buona notizia, Martin). Penso che scoprirai che se lo risolvi, il regex di istanza (non compilato) batterà ogni volta le chiamate statiche. Dovresti anche scoprire che, date le mie conclusioni sopra, anche l'istanza compilata batterà quella non compilata.

MA : dovresti davvero leggere Il post di Jeff Atwood sulle regex compilate prima di applicare ciecamente quell'opzione a ogni regex che crei.

Se abbini costantemente la stessa stringa utilizzando lo stesso modello, ciò potrebbe spiegare perché una versione memorizzata nella cache è leggermente più veloce di una versione compilata.

Questo è dalla documentazione;

https://msdn.microsoft.com /en-us/library/gg578045(v=vs.110).aspx

  

quando viene chiamato un metodo espressione regolare statica e il normale   espressione non può essere trovata nella cache, il motore delle espressioni regolari   converte l'espressione regolare in una serie di codici operativi e negozi   nella cache . Quindi converte questi codici operativi in ??MSIL   che il compilatore JIT può eseguirli. Interpretato regolarmente   le espressioni riducono i tempi di avvio al costo di tempi di esecuzione più lenti .   Per questo motivo, sono utilizzati al meglio quando l'espressione regolare è   utilizzato in un numero limitato di chiamate di metodo o se il numero esatto di   le chiamate ai metodi di espressione regolare sono sconosciute ma dovrebbero esserlo   piccolo. All'aumentare del numero di chiamate al metodo, aumenta il rendimento   dal tempo di avvio ridotto viene superato dall'esecuzione più lenta   velocità.

     

Contrariamente alle espressioni regolari interpretate, compilato regolare   le espressioni aumentano il tempo di avvio ma eseguono singoli   metodi di corrispondenza dei modelli più veloci . Di conseguenza, il vantaggio in termini di prestazioni   che risulta dalla compilazione dell'espressione regolare aumenta in   proporzione al numero di metodi di espressione regolare chiamati.


  

Per riassumere, ti consigliamo di utilizzare espressioni regolari interpretate quando chiami metodi di espressione regolari con una specifica   espressione regolare relativamente poco frequente.

     

Dovresti usare espressioni regolari compilate quando chiami regolarmente   metodi di espressione con un'espressione regolare specifica relativamente   frequentemente.


Come rilevare?

  

La soglia esatta alla quale rallenta l'esecuzione più lenta   le espressioni regolari interpretate superano i guadagni dal loro ridotto   tempo di avvio o la soglia alla quale i tempi di avvio più lenti di   le espressioni regolari compilate superano i guadagni dal loro più veloce   velocità di esecuzione, è difficile da determinare. Dipende da una varietà   di fattori, tra cui la complessità dell'espressione regolare e il   dati specifici che elabora. Per determinare se interpretato o   le espressioni regolari compilate offrono le migliori prestazioni per il tuo   particolare scenario applicativo, è possibile utilizzare la classe Cronometro per   confrontare i loro tempi di esecuzione .


Espressioni regolari compilate:

  

Si consiglia di compilare espressioni regolari in un assembly in   le seguenti situazioni:

     
      
  1. Se sei uno sviluppatore di componenti che lo desidera   per creare una libreria di espressioni regolari riutilizzabili.
  2.   
  3. Se ti aspetti   i metodi di corrispondenza dei pattern della tua espressione regolare da chiamare un   numero indeterminato di volte - ovunque da una o due volte a   migliaia o decine di migliaia di volte. A differenza di compilato o   espressioni regolari interpretate, espressioni regolari che vengono compilate   separare gli assiemi offre prestazioni coerenti a prescindere   del numero di chiamate al metodo.
  4.   
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top