2015-07-24 25 views
8

UPDATE: Il seguente bug sembra essere risolto con PowerShell 5. Il bug rimane in 3 e 4. Quindi non elaborare nessun file enorme con la pipeline a meno che non si stia eseguendo PowerShell 2 o 5.Nessun garbage collection mentre la pipeline PowerShell è in esecuzione


si consideri il seguente frammento di codice:

function Get-DummyData() { 
    for ($i = 0; $i -lt 10000000; $i++) { 
     "This is freaking huge!! I'm a ninja! More words, yay!" 
    } 
} 

Get-DummyData | Out-Null 

Questo causerà PowerShell utilizzo della memoria a crescere in maniera incontrollata. Dopo aver eseguito Get-DummyData | Out-Null un paio di volte, ho visto l'utilizzo della memoria di PowerShell arrivare fino a 4 GB.

In base allo ANTS Memory Profiler, abbiamo un sacco di cose da fare nella coda di finalizzazione del garbage collector. Quando chiamo [GC]::Collect(), la memoria passa da 4 GB a soli 70 MB. Quindi non abbiamo una perdita di memoria, in senso stretto.

Ora, non è abbastanza buono per me essere in grado di chiamare [GC]::Collect() quando ho finito con un'operazione di pipeline di lunga durata. Ho bisogno di garbage collection per accadere durante il un'operazione di pipeline. Tuttavia, se provo a richiamare [GC]::Collect() mentre il gasdotto è in esecuzione ...

function Get-DummyData() { 
    for ($i = 0; $i -lt 10000000; $i++) { 
     "This is freaking huge!! I'm a ninja! More words, yay!" 

     if ($i % 1000000 -eq 0) { 
      Write-Host "Prompting a garbage collection..." 
      [GC]::Collect() 
     } 
    } 
} 

Get-DummyData | Out-Null 

... il problema rimane. L'utilizzo della memoria cresce di nuovo in modo incontrollabile. Ho provato diverse varianti di questo, come ad esempio l'aggiunta di [GC]::WaitForPendingFinalizers(), Start-Sleep -Seconds 10, ecc. Ho provato a cambiare il garbage collector latency modes e forzando PowerShell a utilizzare server garbage collection senza alcun risultato. Non riesco proprio a far sì che il garbage collector faccia la sua parte mentre la pipeline è in esecuzione.

Questo non è affatto un problema in PowerShell 2.0. È anche interessante notare che anche $null = Get-DummyData sembra funzionare senza problemi di memoria. Quindi sembra legato alla pipeline, piuttosto che il fatto che stiamo generando tonnellate di stringhe.

Come è possibile evitare che la memoria si sviluppi in modo incontrollato durante le lunghe condotte?

Nota a margine:

mio Get-DummyData funzione è solo a scopo dimostrativo. Il mio problema reale è che non riesco a leggere file di grandi dimensioni in PowerShell usando Get-Content o Import-Csv. No, sono non memorizzando il contenuto di questi file in variabili. Sono strictly using the pipeline come dovrei. Get-Content .\super-huge-file.txt | Out-Null produce lo stesso problema.

+0

Sembra un po 'come http://stackoverflow.com/q/30918020/258523. –

+0

La parte di esaurimento della memoria sembra un bug. È possibile ridurre in modo significativo il tempo della CPU evitando piping/enumerando 10 milioni di oggetti utilizzando assunzioni, casting o enumerazione delle proprietà –

+0

Non riesco a riprodurre il problema con lo snippet di codice fornito. –

risposta

7

Un paio di cose da segnalare qui. In primo luogo, le chiamate GC funzionano nella pipeline. Ecco uno script oleodotto che richiama solo il GC:

1..10 | Foreach {[System.GC]::Collect()} 

Ecco il grafico Perfmon del GC durante il tempo lo script ha funzionato:

enter image description here

Tuttavia, solo perché si richiama il GC doesn' t significa che l'utilizzo della memoria privata tornerà al valore che avevi prima dell'avvio dello script. Una raccolta di GC raccoglierà solo memoria che non viene più utilizzata. Se esiste un riferimento con radice a un oggetto, non può essere raccolto (liberato). Quindi, mentre i sistemi GC in genere non perdono nel senso C/C++, possono avere accumuli di memoria che si aggrappano ad oggetti più a lungo di quanto dovrebbero.

Nel guardare questo con un profiler di memoria sembra la maggior parte della memoria in eccesso è occupato da una copia della stringa con il parametro informazioni vincolanti:

enter image description here

La radice di queste stringhe sembrano questo:

enter image description here

mi chiedo se c'è qualche funzionalità di registrazione che sta causando PowerShell per aggrapparsi una stringa-ized forma di pipeline oggetti legati?

BTW in questo caso specifico, è molto più efficiente della memoria da assegnare a $ null a ignorare l'output:

$null = GetDummyData 

Inoltre, se è necessario modificare semplicemente un file, controlla il comando Edit-File in the PowerShell Community Extensions 3.2.0. Dovrebbe essere efficiente in termini di memoria se non si utilizza il parametro switch SingleString.

+1

L'ho infettato su Connect. Vota qui se vuoi - https://connect.microsoft.com/PowerShell/feedback/details/1599091/event-logging-memory-hoard-when-processing-a-large-number-of-pipeline-objects –

+0

Anche se non risolve esattamente il mio problema, penso che questo chiarisca il fatto che si tratta di un bug che solo MS può risolvere. Grazie per averci speso così tanto. – Phil

+0

Nessun problema. Ho sposato i benefici dello streaming dei dati attraverso la pipeline invece di archiviare tutto in una variabile, non rendendomi conto che PowerShell sta essenzialmente facendo proprio questo - in una certa misura. –

1

Non è affatto raro trovare che i cmdlet nativi non soddisfano perfettamente quando si sta facendo qualcosa di insolito come l'elaborazione di un enorme file di testo. Personalmente, ho trovato a lavorare con file di grandi dimensioni in PowerShell è molto meglio quando si sceneggiatura con System.IO.StreamReader:

$SR = New-Object -TypeName System.IO.StreamReader -ArgumentList 'C:\super-huge-file.txt'; 
while ($line = $SR.ReadLine()) { 
    Do-Stuff $line; 
} 
$SR.Close() | Out-Null; 

Si noti che è necessario utilizzare il percorso assoluto nel ArgumentList. Per me sembra sempre presumere che tu sia nella tua home directory con percorsi relativi.

Get-Content significa semplicemente leggere l'intero oggetto in memoria come matrice e quindi emetterlo. Penso che chiami semplicemente System.IO.File.ReadAllLines().

Non so in alcun modo che Powershell scartini gli elementi dalla pipeline immediatamente dopo il completamento o che una funzione possa restituire gli articoli in modo asincrono, quindi conserva l'ordine. Potrebbe non permetterlo perché non ha un modo naturale per dire che l'oggetto non verrà usato in seguito, o che gli oggetti successivi non avranno bisogno di fare riferimento a oggetti precedenti.

L'altra cosa bella di PowerShell è che spesso è possibile anche adopt the C# answers. Non ho mai provato File.ReadLines, ma sembra che potrebbe essere abbastanza facile da usare, anche.

+1

Anche con l'approccio StreamReader, il fatto che si stiano spingendo le stringhe attraverso la pipeline causa il problema. Inoltre, non penso che Get-Content restituisca una semplice serie di stringhe. L'ho usato in passato in PowerShell 2.0 per elaborare centinaia di megabyte con un utilizzo di memoria trascurabile. – Phil

+1

@Phil La chiave con l'approccio StreamReader è che non si sta utilizzando affatto una pipeline. Stai leggendo il file riga per riga invece di leggere l'intero file e convogliare il contenuto.Stai facendo tutto ciò che ti serve dove ho "Do-Stuff $ line;". Il problema è che non è possibile accedere a due linee contemporaneamente e le prestazioni potrebbero essere peggiori dal momento che il proprio IO può causare un collo di bottiglia, ma in cambio non si utilizza praticamente memoria. Una ricerca su Google rivelerà che molte persone hanno problemi di memoria con Get-Content, comunque. 'Get-Content | [...] 'ha un utilizzo di memoria diverso da' $ x = Get-Content', e non è chiaro –