2012-12-10 10 views
10

Problema: File molto molto grande, ho bisogno di analizzare riga per riga per ottenere 3 valori da ciascuna riga. Tutto funziona ma richiede molto tempo per analizzare l'intero file. È possibile farlo in pochi secondi? Il tempo tipico è tra 1 minuto e 2 minuti.Esiste un modo veloce per analizzare un file di grandi dimensioni con espressioni regolari?

dimensione del file di esempio è 148,208KB

Sto usando regex per analizzare attraverso ogni linea:

Ecco il mio codice C#:

private static void ReadTheLines(int max, Responder rp, string inputFile) 
{ 
    List<int> rate = new List<int>(); 
    double counter = 1; 
    try 
    { 
     using (var sr = new StreamReader(inputFile, Encoding.UTF8, true, 1024)) 
     { 
      string line; 
      Console.WriteLine("Reading...."); 
      while ((line = sr.ReadLine()) != null) 
      { 
       if (counter <= max) 
       { 
        counter++; 
        rate = rp.GetRateLine(line); 
       } 
       else if (max == 0) 
       { 
        counter++; 
        rate = rp.GetRateLine(line); 
       } 
      } 
      rp.GetRate(rate); 
      Console.ReadLine(); 
     } 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine("The file could not be read:"); 
     Console.WriteLine(e.Message); 
    } 
} 

Ecco la mia espressione regolare:

public List<int> GetRateLine(string justALine) 
{ 
    const string reg = @"^\d{1,}.+\[(.*)\s[\-]\d{1,}].+GET.*HTTP.*\d{3}[\s](\d{1,})[\s](\d{1,})$"; 
    Match match = Regex.Match(justALine, reg, 
           RegexOptions.IgnoreCase); 

    // Here we check the Match instance. 
    if (match.Success) 
    { 
     // Finally, we get the Group value and display it. 

     string theRate = match.Groups[3].Value; 
     Ratestorage.Add(Convert.ToInt32(theRate)); 
    } 
    else 
    { 
     Ratestorage.Add(0); 
    } 
    return Ratestorage; 
} 

Ecco una riga di esempio da analizzare, in genere circa 200.000 righe:

10.10.10.10 - - [27/Nov/2002: 16: 46: 20 -0500] "GET/solr/HTTP/1.1" 200 4926 789

+0

io non sono davvero un esperto, ma non si vede nulla fuori posto. – Almo

+0

risposta breve: no, non è possibile analizzare ogni riga di 150 mb di dati in pochi secondi –

+0

Sì, è quello che ho pensato anch'io, ma non ero sicuro di non essere abbastanza intelligente da pensare a qualche notazione O grande per renderlo più veloce. – Rayshawn

risposta

16

Memory Mapped Files e Task Parallel Library aiuto.

  1. Creare MMF persistente con più viste di accesso casuale. Ogni vista corrisponde ad una particolare parte di un file
  2. Definire metodo di analisi con parametri come IEnumerable<string>, fondamentalmente per estrarre un insieme di linee non parsed
  3. creare e avviare una TPL compito per una MMF vista con Parse(IEnumerable<string>) come azione Task
  4. Ognuno dei compiti dei lavoratori aggiunge un dati analizzati nella coda condivisa di BlockingCollection tipo
  5. un altro compito ascoltare aC (GetConsumingEnumerable()) ed elabora tutti i dati che già analizzati dalla task worker

Vedi Pipelines pattern su MSDN

devo dire questa soluzione è per .NET Framework >=4

4

In questo momento, si ricrea la tua Regex ogni volta che si chiama GetRateLine, che si verifica ogni volta che si legge una riga.

Se si create a Regex instance una volta in anticipo e quindi si utilizza il metodo non statico Match, si salverà sul tempo di compilazione delle espressioni regolari, che potrebbe potenzialmente fornire un aumento di velocità.

Detto questo, è probabile che non vi porterà da minuti a secondi ...

1

Invece di ricreare una regex per ogni chiamata a GetRateLine, creare in anticipo, passando l'opzione RegexOptions.Compiled al costruttore Regex(String,RegexOptions).

Si potrebbe anche voler provare a leggere l'intero file in memoria, ma dubito che questo sia il collo di bottiglia. Non dovrebbe richiedere un minuto per leggere in ~ 100 MB dal disco.

1

A una breve occhiata ci sono alcune cose che vorrei provare ...

In primo luogo, aumentare il buffer di flusso di file ad almeno 64kb:

using (var sr = new StreamReader(inputFile, Encoding.UTF8, true, 65536)) 

In secondo luogo, costruire il Regex una volta invece di utilizzare una stringa all'interno del ciclo:

static readonly Regex rateExpression = new Regex(@"^\d{1,}.+\[(.*)\s[\-]\d{1,}].+GET.*HTTP.*\d{3}[\s](\d{1,})[\s](\d{1,})$", RegexOptions.IgnoreCase); 
//In GetRateLine() change to: 
Match match = rateExpression.Match(justALine); 

In terzo luogo, utilizzare un unico elenca l'istanza avendo Responder.GetRate() restituisce una lista o un array.

// replace: 'rp.GetRate(rate)', with: 
rate = rp.GetRate(); 

vorrei preallocare lista per un limite 'ragionevole':

List<int> rate = new List<int>(10000); 

Si potrebbe anche pensare di cambiare la codifica da UTF-8 a ASCII se disponibili e applicabili alle proprie esigenze specifiche.

Commenti

In generale, se questo è davvero ad essere un requisito per ottenere il tempo di analisi verso il basso, si sta andando a voler costruire un tokenizzatore e saltare Regex del tutto. Dal momento che il tuo formato di input sembra essere tutto ascii e abbastanza semplice, questo dovrebbe essere abbastanza facile da fare, ma probabilmente un po 'più fragile della regex. Alla fine dovrai pesare e bilanciare il bisogno di velocità rispetto all'affidabilità e alla manutenibilità del codice.

Se avete bisogno di qualche esempio a mano guardare analisi al answer to this question

Problemi correlati