2015-09-03 18 views
5

I file di registro Apache standard, con dimensioni comprese tra 500 Mb e 2 GB. Ho bisogno di ordinare le linee in loro (ogni riga inizia con una data AAAA-MM-GG HH: mm: ss, in modo che nessun trattamento necessario per l'ordinamentoOrdinare file di testo di grandi dimensioni in PowerShell

La cosa più semplice e più ovvia che viene in mente è

.
Get-Content unsorted.txt | sort | get-unique > sorted.txt 

sto indovinando (senza aver provato) che facendo questo utilizzando Get-Content avrebbe preso per sempre nei miei file da 1 GB. non si sa bene il mio modo per aggirare System.IO.StreamReader, ma io sono curioso di sapere se una soluzione efficace potrebbe essere messo insieme usando quello?

Grazie a chiunque potrebbe avere un'idea più efficiente.

[modifica]

Ho provato questo in seguito, e ci è voluto molto tempo; circa 10 minuti per 400 MB.

+0

Ho provato il comando precedente e in effetti ci è voluto molto tempo (circa 10 minuti su 460 MB), e il risultato finale non era quello che mi serviva, più il file di destinazione ('sorted.txt') aveva dimensioni doppie della fonte. –

+0

Le differenze di dimensioni sono probabilmente dovute a diverse codifiche utilizzate. Sostituendo '> sorted.txt' con qualcosa come' | Set-Content sorted.txt' potrebbe fare il trucco, altrimenti potresti provare '| Out-File sorted.txt -Encoding '. – notjustme

+0

Utilizzando il tuo suggerimento '| Set-Content sorted.txt' ha aiutato a risolverlo correttamente, ma è ancora piuttosto lento. Aggiungere '-ReadCount 5000' dopo che 'Get-Content' rende è molto più veloce, ma l'ordinamento è rotto. Sto indovinando, per ordinare correttamente, dobbiamo leggere riga per riga, piuttosto che un blocco alla volta ... Vorrei che ci fosse un modo più efficiente. –

risposta

5

Get-Content è terribilmente inefficace per la lettura di file di grandi dimensioni. Sort-Object non è molto veloce.

Facciamo istituito una linea di base:

$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
$c = Get-Content .\log3.txt -Encoding Ascii 
$sw.Stop(); 
Write-Output ("Reading took {0}" -f $sw.Elapsed); 

$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
$s = $c | Sort-Object; 
$sw.Stop(); 
Write-Output ("Sorting took {0}" -f $sw.Elapsed); 

$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
$u = $s | Get-Unique 
$sw.Stop(); 
Write-Output ("uniq took {0}" -f $sw.Elapsed); 

$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
$u | Out-File 'result.txt' -Encoding ascii 
$sw.Stop(); 
Write-Output ("saving took {0}" -f $sw.Elapsed); 

Con un file di 40 MB con 1,6 milioni di linee (fatta di 100k linee uniche ripetuto 16 volte) questo script produce il seguente output sulla mia macchina:

Reading took 00:02:16.5768663 
Sorting took 00:02:04.0416976 
uniq took 00:01:41.4630661 
saving took 00:00:37.1630663 

Totalmente insignificante: più di 6 minuti per ordinare file minuscoli. Ogni passo può essere migliorato molto. Usiamo StreamReader per leggere il file riga per riga in HashSet che rimuoverà i duplicati, quindi copi i dati in List e li smistiamo lì, quindi usa StreamWriter per scaricare i risultati.

$hs = new-object System.Collections.Generic.HashSet[string] 
$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
$reader = [System.IO.File]::OpenText("D:\log3.txt") 
try { 
    while (($line = $reader.ReadLine()) -ne $null) 
    { 
     $t = $hs.Add($line) 
    } 
} 
finally { 
    $reader.Close() 
} 
$sw.Stop(); 
Write-Output ("read-uniq took {0}" -f $sw.Elapsed); 

$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
$ls = new-object system.collections.generic.List[string] $hs; 
$ls.Sort(); 
$sw.Stop(); 
Write-Output ("sorting took {0}" -f $sw.Elapsed); 

$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
try 
{ 
    $f = New-Object System.IO.StreamWriter "d:\result2.txt"; 
    foreach ($s in $ls) 
    { 
     $f.WriteLine($s); 
    } 
} 
finally 
{ 
    $f.Close(); 
} 
$sw.Stop(); 
Write-Output ("saving took {0}" -f $sw.Elapsed); 

questo script produce:

read-uniq took 00:00:32.2225181 
sorting took 00:00:00.2378838 
saving took 00:00:01.0724802 

Sullo stesso file di input viene eseguito più di 10 volte più veloce. Sono ancora sorpreso anche se ci vogliono 30 secondi per leggere il file dal disco.

+0

Invia prova a Measure-Command: https://technet.microsoft.com/en-us/library/Hh849910.aspx?f=255&MSPPError=-2147217396 –

+0

Questo è un miglioramento significativo delle prestazioni, tuttavia, il file di destinazione è notevolmente più piccolo rispetto alla fonte. Le voci duplicate sembrano cancellate, cosa che non voglio che faccia. Tutto ciò di cui ho bisogno è di ordinare le righe in ordine alfabetico; se ci sono più linee identiche, tienile tutte. Grazie per l'aiuto! –

+0

Il codice di esempio chiamato "Get-Unique" che rimuove i duplicati. Se non ne hai bisogno, leggi semplicemente "Elenco" e ordina, non c'è bisogno di usare "HashSet" qui. – n0rd

0

(a cura di essere più chiara, secondo i commenti di n0rd)

E 'potrebbe essere un problema di memoria. Dal momento che stai caricando l'intero file in memoria per ordinarlo (e aggiungendo l'overhead della pipa in Sort-Object e la pipa in Get-Unique), è possibile che stai colpendo i limiti di memoria della macchina e forzandola alla pagina su disco, che rallenterà molto le cose. Una cosa che potresti prendere in considerazione è quella di suddividere i registri prima di ordinarli, e quindi ricollegarli.

Probabilmente questo non corrisponde esattamente al tuo formato, ma se ho un file di registro di grandi dimensioni per, diciamo, 8/16/2012 che si estende per diverse ore, posso dividerlo in un file diverso per ogni ora usando qualcosa di simile:

for($i=0; $i -le 23; $i++){ Get-Content .\u_ex120816.log | ? { $_ -match "^2012-08-16 $i`:" } | Set-Content -Path "$i.log" } 

questo sta creando un'espressione regolare per ogni ora di quel giorno e il dumping tutte le voci di registro corrispondenti in un file di log più piccolo chiamato a ore (ad esempio 16.log, 17.log) .

Allora posso eseguire il processo di selezione e ottenere voci univoche su un molto più piccoli sottoinsiemi, che dovrebbe correre molto più veloce:

for($i=0; $i -le 23; $i++){ Get-Content "$i.log" | sort | get-unique > "$isorted.txt" } 

E poi si possono unire di nuovo insieme.

A seconda della frequenza dei registri, potrebbe essere più sensato dividerli di giorno o minuto; la cosa principale è metterli in pezzi più gestibili per l'ordinamento.

Ancora una volta, questo ha senso solo se si stanno colpendo i limiti di memoria della macchina (o se Sort-Object utilizza un algoritmo davvero inefficiente).

+0

l'ordinamento di un grosso blocco non è più lento di molti blocchi più piccoli, a condizione che tutti i dati si adattino alla memoria (ovvero non si rovesci nulla da scambiare) – n0rd

+0

@ n0rd - dipenderà dalla dimensione del file, dalla quantità di memoria disponibile, dall'algoritmo L'oggetto Sort-Object si usa e quanto vicino è ordinato i dati è in anticipo. –

+0

Sugli stessi dati di input, l'ordinamento dell'intero set non sarà mai più lento di ordinare i pezzi con lo stesso algoritmo e quindi unirli. Per l'ordinamento esterno (quando tutti i dati non rientrano nella memoria), sì, è necessario dividere, ordinare e unire. Altrimenti non c'è alcun guadagno per farlo. – n0rd

0

Se ogni riga del log è preceduta da un timestamp e i messaggi di registro non contengono newline incorporate (che richiederebbe una gestione speciale), penso che ci vorrebbe meno memoria e tempo di esecuzione per convertire il timestamp da [String] a [DateTime] prima di ordinare. Di seguito si presuppone ogni voce di registro è del formato yyyy-MM-dd HH:mm:ss: <Message> (si noti che il HH format specifier viene utilizzato per un orologio di 24 ore):

Get-Content unsorted.txt 
    | ForEach-Object { 
     # Ignore empty lines; can substitute with [String]::IsNullOrWhitespace($_) on PowerShell 3.0 and above 
     if (-not [String]::IsNullOrEmpty($_)) 
     { 
      # Split into at most two fields, even if the message itself contains ': ' 
      [String[]] $fields = $_ -split ': ', 2; 

      return New-Object -TypeName 'PSObject' -Property @{ 
       Timestamp = [DateTime] $fields[0]; 
       Message = $fields[1]; 
      }; 
     } 
    } | Sort-Object -Property 'Timestamp', 'Message'; 

Se si sta elaborando il file di input per la visualizzazione interattiva è possibile reindirizzare il sopra in Out-GridView o Format-Table per visualizzare i risultati.Se è necessario salvare i risultati ordinati è possibile reindirizzare il sopra in quanto segue:

| ForEach-Object { 
     # Reconstruct the log entry format of the input file 
     return '{0:yyyy-MM-dd HH:mm:ss}: {1}' -f $_.Timestamp, $_.Message; 
    } ` 
    | Out-File -Encoding 'UTF8' -FilePath 'sorted.txt';