2009-04-29 2 views
5

Ich erzeuge reguläre Ausdrücke dynamisch, indem ich durch eine XML-Struktur laufe und die Anweisung aufbaue, während ich durch ihre Knotentypen blicke. Ich verwende diesen regulären Ausdruck als Teil eines von mir definierten Layout-Typs. Ich analysiere dann eine Textdatei, die am Anfang jeder Zeile eine ID hat. Diese ID weist mich auf ein bestimmtes Layout hin. Ich versuche dann, die Daten in dieser Zeile mit seiner Regex zu vergleichen.Dynamisch aufgebaute reguläre Ausdrücke laufen extrem langsam!

Klingt gut und dandy richtig? Das einzige Problem ist, dass die Strings extrem langsam sind. Ich habe sie als kompiliert gesetzt, um die Dinge ein wenig zu beschleunigen, aber ohne Erfolg. Was verwirrend ist, ist, dass diese Ausdrücke nicht so komplex sind. Ich bin auf keinen Fall ein RegEx-Guru, aber ich kenne eine anständige Menge über sie, um die Dinge gut gehen zu lassen. Hier

ist der Code, der die Ausdrücke erzeugt ...

StringBuilder sb = new StringBuilder(); 
//get layout id and memberkey in there... 
sb.Append(@"^([0-9]+)[ \t]{1,2}([0-9]+)"); 
foreach (ColumnDef c in columns) 
{ 
    sb.Append(@"[ \t]{1,2}"); 
    switch (c.Variable.PrimType) 
    { 
     case PrimitiveType.BIT: 
      sb.Append("(0|1)"); 
      break; 
     case PrimitiveType.DATE: 
      sb.Append(@"([0-9]{2}/[0-9]{2}/[0-9]{4})"); 
      break; 
     case PrimitiveType.FLOAT: 
      sb.Append(@"([-+]?[0-9]*\.?[0-9]+)"); 
      break; 
     case PrimitiveType.INTEGER: 
      sb.Append(@"([0-9]+)"); 
      break; 
     case PrimitiveType.STRING: 
      sb.Append(@"([a-zA-Z0-9]*)"); 
      break; 
    } 
} 
sb.Append("$"); 
_pattern = new Regex(sb.ToString(), RegexOptions.Compiled); 

Der eigentliche langsame Teil ...

public System.Text.RegularExpressions.Match Match(string input) 
{ 
    if (input == null) 
     throw new ArgumentNullException("input"); 

    return _pattern.Match(input); 
} 

Ein typisches "_pattern" kann etwa 40 bis 50 Spalten haben. Ich erspare mir das Einfügen des gesamten Musters. Ich versuche, jeden Fall so zu gruppieren, dass ich später im Match-Objekt über jeden Fall aufzählen kann.

Irgendwelche Tipps oder Modifikationen, die drastisch helfen könnte? Oder läuft das langsam zu erwarten?

EDIT FÜR CLARITY: Sorry, ich glaube nicht, dass ich klar genug das erste Mal war.

Ich verwende eine XML-Datei regex die für ein bestimmtes Layout zu erzeugen. Ich durchlaufe dann eine Datei für einen Datenimport. Ich muss sicherstellen, dass jede Zeile in der Datei dem Muster entspricht, das es angeblich sein soll. So können Muster gegen mehrere Male überprüft werden, möglicherweise Tausende.

+0

Sie könnten [01] für BIT versuchen. – Dave

+0

Versuchen Sie, CSV mit diesem Regex abzugleichen? – Gumbo

+0

Seine Tab-getrennte Ausgabe von SAS –

Antwort

8

Sie analysieren eine 50-Spalten-CSV-Datei (die Registerkarten verwendet) mit Regex?

Sie sollten nur doppelte Tabs entfernen, dann teilen Sie den Text auf \ t. Jetzt haben Sie alle Ihre Spalten in einem Array.Sie können Ihre ColumnDef-Objektauflistung verwenden, um Sie über die einzelnen Spalten zu informieren.

Bearbeiten: Sobald Sie Dinge aufgeteilt haben, können Sie optional regex verwenden, um jeden Wert zu überprüfen, sollte dies viel schneller sein als die Verwendung der riesigen einzelnen Regex.

Edit2: Sie erhalten auch einen zusätzlichen Vorteil, wenn Sie genau wissen, welche Spalte schlecht formatiert ist, und Sie können einen Fehler wie "Sytax error in Spalte 30 in Zeile 12, erwartetes Datumsformat" erzeugen.

+0

Ich fange an zu denken, dass dies viel schneller und einfacher sein wird. –

+0

Dies ist wahrscheinlich die beste Lösung, die bisher vorgestellt wurde.Ich benutze täglich Dutzende von komplexen Regexes (Verarbeitung von Text und XML). IME, sobald Ihre Regex eine gewisse "kritische Masse" an Komplexität erreicht haben, geht die Leistung in den Röhren verloren. Wenn Sie dieses Problem in kleinere Teile aufteilen, können Sie diesen Engpass umgehen. – patjbs

1

ein Potential von 50 Match-Gruppen in einem einzigen Ausdruck von Standard Nachdem geht ein bisschen langsam. Ich würde ein paar Dinge tun, um zu sehen, ob Sie den Leistungsrückgang festhalten können.

  1. Beginnen Sie mit einem hart codierten, vs dynamischen Vergleich und sehen Sie, ob Sie irgendwelche Leistungsvorteile haben.
  2. Sehen Sie sich Ihre Anforderungen an und prüfen Sie, ob Sie die Anzahl der zu bewertenden Gruppierungen reduzieren können.
  3. Verwenden Sie bei Bedarf ein Profiler-Tool, z. B. Ants Profiler, um den Standort der Verlangsamung anzuzeigen.
2

Well. Wenn Sie das Muster mit einem StringBuilder erstellen, sparen Sie einige Zyklen, verglichen mit der Verkettung.

Eine Optimierung auf diese, die drastisch ist (kann sichtbar gemessen werden) wird am gehen wahrscheinlich tun dies durch eine andere Methode sein.

Reguläre Ausdrücke sind langsam ... leistungsstark, aber langsam. Das Durchsuchen einer Textdatei und der Vergleich mit regulären Ausdrücken, nur um die richtigen Daten zu erhalten, sind nicht sehr schnell (abhängig vom Host-Computer und der Größe der Textdatei).

Vielleicht wäre es effizienter, die Daten in einer anderen Methode als in einer großen Textdatei zu speichern (verwenden Sie dazu auch XML?) Oder vielleicht eine durch Kommas getrennte Liste.

+0

ist es für den Datenimport tatsächlich. Deshalb verwende ich Regex. Um sicherzustellen, dass die importierten Daten dem vom Benutzer angegebenen Format entsprechen. –

+0

Wenn Sie sagen, es ist langsam, wie langsam meinst du? Wie groß sind diese Dateien? Mit wie vielen Mustern vergleichen Sie? Vermutlich gibt es einige ziemlich häufige Muster, die mit ifs und substrings gemacht werden könnten. Es wäre wahrscheinlich effizienter (wenn auch weniger sauber), wenn Sie RegEx nicht für allgemeine Muster verwenden, sondern sie selbst in die App einprogrammieren. Übrigens wird diese Funktionalität regelmäßig verwendet, wenn dies der Fall ist, muss es eine effizientere Möglichkeit geben, diese Daten zu speichern, wo immer sie direkt hingehen (Datenbank?), Und anschließend eine Validierung durchzuführen. –

4

Regulärer Ausdruck ist teuer zu erstellen und noch teurer, wenn Sie sie kompilieren. Das Problem besteht also darin, dass Sie viele reguläre Ausdrücke erstellen, diese jedoch nur einmal verwenden.

Sie sollten sie zur Wiederverwendung zwischenspeichern und nicht kompilieren, wenn Sie sie nicht wirklich oft verwenden möchten. Ich habe das nie gemessen, aber ich könnte mir vorstellen, dass Sie mehr als 100 Mal einen einfachen regulären Ausdruck verwenden müssen, um die Kosten für die Zusammenstellung zu übertreffen.

Leistungstest

  • Regex: "^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+(?:[a-z]{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)$"

  • Eingang: "www.stackoverflow.com"

  • Ergebnisse in Millisekunden pro Iteration

    • ein regex , zusammengestellt, 10.000 Iterationen: 0,0018 ms
    • eine regex, nicht kompiliert, 10.000 Iterationen: 0,0021 ms
    • eine Regex pro Iteration nicht kompiliert, 10.000 Iterationen: 0,0287 ms
    • eine Regex pro Iteration , kompiliert 10.000 Iterationen: 4,8144 ms

Beachten Sie, dass auch nach 10, 000 Iterationen die kompilierte und nicht kompilierte Regex sind immer noch sehr nahe zusammen, um ihre Leistung zu vergleichen. Mit zunehmender Anzahl von Iterationen schneidet die kompilierte Regex besser ab.

  • eine regex, kompiliert 1.000.000 Iterationen: 0,00137 ms
  • eine regex, nicht kompiliert, 1.000.000 Iterationen: 0,00225 ms
+0

Vielleicht musste ich etwas besser erklären. Ich benutze sie nicht nur einmal. Jedes Mal, wenn etwas in der analysierten Datei auf ein bestimmtes Layout verweist. Ich überprüfe, ob die Linie mit einem Muster für dieses Layout übereinstimmt. –

+0

Precompiling, sogar für den einmaligen Gebrauch, ergibt bei meinen Tests durchweg bessere Regex-Performance. Es ist nicht viel für den Einmalgebrauch, aber es gibt keinen Leistungseinbruch. – patjbs

+0

Sie erstellen also einen regulären Ausdruck pro Layout und verwenden dieses, wenn Sie eine entsprechende Zeile finden, richtig? –

5

Einige Leistungs Gedanken:

  • Verwenden Sie anstelle von (0|1)
  • Verwendung nicht-einfangenden Gruppen (?:expr) statt einfangenden Gruppen (wenn Sie wirklich brauchen Gruppierung)

bearbeiten Wie es, dass Ihre Werte scheint durch Leerzeichen getrennt sind, warum nicht teilen Sie es dort auf?

+0

Ya, Es könnte tatsächlich günstiger sein, eine Liste von kleineren Regex pro Layout zu halten, und teilen Sie die Zeichenfolge basierend auf '\ t' dann gehen Sie nach unten passend zu jedem. –

1

Ich würde einfach einen Lexer von Hand bauen.

In diesem Fall sieht es so aus, als hätten Sie eine Reihe von Feldern, die durch Tabulatoren voneinander getrennt sind, wobei ein Eintrag durch eine neue Zeile beendet wird. Die XML-Datei scheint die Reihenfolge der Spalten und deren Typen zu beschreiben.

Schreiben Code, um jeden Fall von Hand zu erkennen, ist wahrscheinlich im schlimmsten Fall 5-10 Zeilen Code.

Sie möchten dann einfach ein Array von PrimitiveType [] -Werten aus der XML-Datei generieren und dann die Funktion "GetValues" unten aufrufen.

Dies sollte Ihnen erlauben, einen einzigen Durchlauf durch den Eingabestrom zu machen, was einen großen Schub gegenüber der Verwendung von Regexen geben sollte.

Sie müssen die "ScanXYZ" -Methoden selbst liefern. Sie sollten einfach zu schreiben sein. Es ist am besten, sie ohne Regexes zu implementieren.

public IEnumerable<object[]> GetValues(TextReader reader, PrimitiveType[] schema) 
{ 
    while (reader.Peek() > 0) 
    { 
     var values = new object[schema.Length]; 
     for (int i = 0; i < schema.Length; ++i) 
     { 
      switch (schema[i]) 
      { 
       case PrimitiveType.BIT: 
        values[i] = ScanBit(reader); 
        break; 
       case PrimitiveType.DATE: 
        values[i] = ScanDate(reader); 
        break; 
       case PrimitiveType.FLOAT: 
        values[i] = ScanFloat(reader); 
        break; 
       case PrimitiveType.INTEGER: 
        values[i] = ScanInt(reader); 
        break; 
       case PrimitiveType.STRING: 
        values[i] = ScanString(reader); 
        break; 
      } 
     } 

     EatTabs(reader); 

     if (reader.Peek() == '\n') 
     { 
      break; 
     } 

    if (reader.Peek() == '\n') 
    { 
     reader.Read(); 
    } 
    else if (reader.Peek() >= 0) 
    { 
     throw new Exception("Extra junk detected!"); 
    } 

    yield return values; 

    } 

    reader.Read(); 
}