DPXone
Lieutenant
- Registriert
- Mai 2009
- Beiträge
- 554
[Der Beitrag ist in Bearbeitung und beinhaltet nicht alle Tipps von Anfang an.]
Hey Zusammen,
ich möchte den Leuten, die mit PowerShell zu tun haben und PowerShell-Anfänger sind, hier mal eine Sammlung meiner bisher gesammelten Erfahrungen bezüglich Performance in PowerShell mittteilen.]
Der Guide ist extra im (hoffentlich) leichten Niveau gehalten, damit auch Anfänger damit etwas anfangen können.
Bitte stellt eure Fragen zum Guide hier. Alles was unklar ist wird im Nachgang hier im Guide geändert, damit Nachfolger hoffentlich weniger Probleme haben.
Sollte euch das Englisch in den Skripten stören, das mach ich leider generell aufgrund Internationalität . Wir klären das hier aber gerne. Meldet euch einfach.
Ich hoffe, dass ich damit dem ein oder anderen helfen kann
Bei Fragen, Anmerkungen oder weiteren Performance-Tipps --> Meldet euch.
Hinweis
Die Beispiel-Skripte sind in sogenannte Regionen "#region" eingeteilt.
(Regionen werden durch das Zeichen und Statement "#region" pro Zeile dargestellt)
Voraussetzung für meine Aufführungen, Bemerkungen, Scripts und Ergebnisse ist PowerShell 5.1, da es bis dato doch sehr viel Performance-Verbesserungen bei diversen Ansätzen gegeben hat.
PowerShell 5.1 bekommt ihr durch das Windows Management Framework 5.1 (Kürzform: WMF):
https://www.microsoft.com/en-us/download/details.aspx?id=54616
Was neuere WMF-Versionen so bewirken können, hier ein Beispiel aus diesem Forum (siehe die Antwort an mich (@DPXone) in "Ergänzung (10. Juli 2017)" in Bezug auf Windows Management Framework 5.1 und Abschlusstext):
https://www.computerbase.de/forum/t...reren-einzelnen-zellen.1693664/#post-20248809
Vorteilhaft ist auch, dass ihr Englisch versteht, da ich die Skript-Beispiele in Englisch dokumentiere.
Die relevanten Code-Ausführen werden in den Beispielen mit Ausrufezeichen gekennzeichnet:
Wenn ihr nicht wisst, was ein "Pipeline-Input" ist:
Pipeline-Input ist die Übergabe von Werten von einem Befehl / einer Funktion zur Nächsten über eine Pipe.
Gekennzeichnet wird die Pipe durch das Zeichen "|" zwischen den Befehlen.
Beispiel:
PowerShell-Performance-Tipps:
Großer Dank für Anmerkungen, weitere Verbesserungen, Korrekturen, Ideen und Co. geht an:
@RalphS
Hey Zusammen,
ich möchte den Leuten, die mit PowerShell zu tun haben und PowerShell-Anfänger sind, hier mal eine Sammlung meiner bisher gesammelten Erfahrungen bezüglich Performance in PowerShell mittteilen.]
Der Guide ist extra im (hoffentlich) leichten Niveau gehalten, damit auch Anfänger damit etwas anfangen können.
Bitte stellt eure Fragen zum Guide hier. Alles was unklar ist wird im Nachgang hier im Guide geändert, damit Nachfolger hoffentlich weniger Probleme haben.
Sollte euch das Englisch in den Skripten stören, das mach ich leider generell aufgrund Internationalität . Wir klären das hier aber gerne. Meldet euch einfach.
Ich hoffe, dass ich damit dem ein oder anderen helfen kann
Bei Fragen, Anmerkungen oder weiteren Performance-Tipps --> Meldet euch.
Hinweis
Die Beispiel-Skripte sind in sogenannte Regionen "#region" eingeteilt.
(Regionen werden durch das Zeichen und Statement "#region" pro Zeile dargestellt)
Voraussetzung für meine Aufführungen, Bemerkungen, Scripts und Ergebnisse ist PowerShell 5.1, da es bis dato doch sehr viel Performance-Verbesserungen bei diversen Ansätzen gegeben hat.
PowerShell 5.1 bekommt ihr durch das Windows Management Framework 5.1 (Kürzform: WMF):
https://www.microsoft.com/en-us/download/details.aspx?id=54616
Was neuere WMF-Versionen so bewirken können, hier ein Beispiel aus diesem Forum (siehe die Antwort an mich (@DPXone) in "Ergänzung (10. Juli 2017)" in Bezug auf Windows Management Framework 5.1 und Abschlusstext):
https://www.computerbase.de/forum/t...reren-einzelnen-zellen.1693664/#post-20248809
Vorteilhaft ist auch, dass ihr Englisch versteht, da ich die Skript-Beispiele in Englisch dokumentiere.
Die relevanten Code-Ausführen werden in den Beispielen mit Ausrufezeichen gekennzeichnet:
PowerShell:
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# This is the execution we're looking at:
Hier steht der CODE CODE CODE CODE CODE CODE
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Wenn ihr nicht wisst, was ein "Pipeline-Input" ist:
Pipeline-Input ist die Übergabe von Werten von einem Befehl / einer Funktion zur Nächsten über eine Pipe.
Gekennzeichnet wird die Pipe durch das Zeichen "|" zwischen den Befehlen.
Beispiel:
PowerShell:
Get-ChildItem 'C:\' | Get-Content
PowerShell-Performance-Tipps:
- Verwendet keine Arrays ( $array= @() ), wenn dem Array ständig Werte hinzugefügt werden ($array+=$BlaBla)!
--> Verwendent die Typen "ArrayList", "GenericList" oder eine direkte Zuweisung zu Variablen. (Siehe Beispiel unten)u
Arrays, initialisiert durch $Array = @(), verwenden viele, weil sie es womöglich nicht anders von PowerShell kennen.
Arrays besitzen eine feste Größe und sind unveränderlich (immutable), das heißt, wenn ihr das Array, wie oben erstmals initialisiert, dann besitzt es eine Größe von 0; direkte Änderungen unmöglich.
Jedes Mal, wenn ihr dann via $Array+=$neuesItem (nennt sich in PowerShell "op_addition") etwas hinzufügt, dann wird das Item (der Wert den ihr hinzufügen möchtet) nicht direkt hinzugefügt, sondern es wird ein komplett neues Array erstellt, mit einer neuen Größe und alle Items des bisherigen Arrays werden rüber kopiert.
Das frisst, je größer das Array wird, enorm an der Performance.
Um das zu Veranschaulichen gibt's unten ein Beispiel und zusätzlich das Ergebnis, welches ingesamt vier Varianten aufzeigt, wie man Objekte und Werte in einer Variable sammeln kann.
Im Beispiel unten wird nach jeder Ausführung auch die Ausführungszeit angezeigt.- Test 1 führt einen Pipeline-Input aus. Hierbei wird "Where-Object" (Alias: ?) verwendet (% / ForEach-Object bewirkt ein ähnliches Ergebnis!).
- Test 2 nutzt die oben erwähnte Variante des Array mit op_addition aus, wobei das Array automatisch jedes Mal neu erstellt wird.
- Test 3 nutzt das "ForEach"-Statement und nutzt eine ArrayList, um neue Werte hinzuzufügen.
- Test 4 nutzt das "ForEach"-Statement und fügt die Werte im Nachgang direkt einer Variablen zu
Info:
"Write-Progress" bewirkt im Beispiel so gut wie keine Performance-Einbußen, wenn man es wie im Beispiel gezielt und ressourcen-schonend (=mit mathematischem modulo --> Zahl % Zahl = 0) einsetzt!
Die Details zum performanten Umgang mit Write-Progress und Write-Host werden später erklärt.
Performance Beispiel 1:
PowerShell:$testInput = 1 .. 200000 # test numbers between 1 and 200.000 $progressMax = @($testInput).count # max count of above variable $progressInterval = [math]::Ceiling($testInput.Count * 0.1) # Feedback in 10% steps for "write-progress". Executing it for each item has a very huge performance impact! $stopwatch = [System.Diagnostics.Stopwatch]::new() # let's get the stopwatch started CLS # clear the screen ... write-host ("`r`n" * 5) # ... and add 5 empty lines [gc]::Collect() # Collecing garbage, for a way of better performance measuring ############################################################################################################################# #region TEST 1: Where-Object #---------------------------------------------------------------------------------------------------------------------------- Write-Host 'TEST 1: Testing Where-Object' Write-Host "`tDisadvantages:" Write-Host "`t- Using Pipeline-Input (medium performance loss compared to the best approach)`r`n" write-host "`tProcessing times:" $stopwatch.Start() $i = -1 # starting with less 1 as common to show write-progress bar at starting point and not just on first hit after 0 #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # This is the execution we're looking at: $testoutput = $testInput | ? { #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #....................................................................................................................... #This is just for progress activity returns $i++ If ($i % $progressInterval -eq 0) { write-progress -Activity 'Processing "Where-Object"' -PercentComplete($i / $progressMax * 100) If ($i -gt 0) { Write-Host ("`t`t{0,-10} to {1,-10} processed in: {2}" -f ($i - $progressInterval) ,($i) , $($stopwatch.Elapsed - $LastTime)) } $LastTime = $stopwatch.Elapsed } #....................................................................................................................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # This is the execution we're looking at: $_ % 3 -eq 0 #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! } $ElapsedTimeTest1 = $stopwatch.Elapsed write-host "`r`n`tTotal time: $($ElapsedTimeTest1.ToString())" #endregion ############################################################################################################################# Write-Host ('*' * 50) Write-Host ("`r`n" * 2) [gc]::Collect() ############################################################################################################################# #region TEST 2: ForEach with op_addition operation #---------------------------------------------------------------------------------------------------------------------------- Write-Host 'TEST 2: Testing ForEach with op_addition for the immutable array / creating new arrays (most used)' Write-Host "`tDisadvantages:" Write-Host "`t- Using an Array although it's of a fixed size (very huge performance loss compared to the best approach)`r`n" write-host "`tProcessing times:" $stopwatch.Restart() $i = -1 # starting with less 1 as common to show write-progress bar at starting point and not just on first hit after 0 #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # This is the execution we're looking at: $testoutput2 = @() Foreach ($item In $testInput) { #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #....................................................................................................................... #This is just for progress activity returns $i++ If ($i % $progressInterval -eq 0) { write-progress -Activity 'Processing "ForEach with op_addition for the immutable array" (See how the perfomance decreases the bigger the array gets)' -PercentComplete($i / $progressMax * 100) If ($i -gt 0) { Write-Host ("`t`t{0,-10} to {1,-10} processed in: {2}" -f ($i - $progressInterval) ,($i) , $($stopwatch.Elapsed - $LastTime)) } $LastTime = $stopwatch.Elapsed } #....................................................................................................................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # This is the execution we're looking at: If ($item % 3 -eq 0) { $testoutput2+= $item } #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! } $ElapsedTimeTest2 = $stopwatch.Elapsed write-host "`r`n`tTotal time: $($ElapsedTimeTest2.ToString())" #endregion ############################################################################################################################# Write-Host ('*' * 50) Write-Host ("`r`n" * 2) [gc]::Collect() ############################################################################################################################# #region TEST 3: ForEach with ArrayList #---------------------------------------------------------------------------------------------------------------------------- Write-Host 'TEST 3: Testing ForEach with ArrayList' Write-Host "`tAdvantages: " Write-Host "`t- Using ArrayList (very low performance loss compared to the best approach)`r`n" write-host "`tProcessing times:" $stopwatch.Restart() $i = -1 # starting with less 1 as common to show write-progress bar at starting point and not just on first hit after 0 #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # This is the execution we're looking at: $testoutput3 = [System.Collections.ArrayList]::new() Foreach ($item In $testInput) { #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #....................................................................................................................... #This is just for progress activity returns $i++ If ($i % $progressInterval -eq 0) { write-progress -Activity 'Processing "Testing ForEach with ArrayList"' -PercentComplete($i / $progressMax * 100) If ($i -gt 0) { Write-Host ("`t`t{0,-10} to {1,-10} processed in: {2}" -f ($i - $progressInterval) ,($i) , $($stopwatch.Elapsed - $LastTime)) } $LastTime = $stopwatch.Elapsed } #....................................................................................................................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # This is the execution we're looking at: If ($item % 3 -eq 0) { [void] $testoutput3.Add($item) } #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! } $ElapsedTimeTest3 = $stopwatch.Elapsed write-host "`r`n`tTotal time: $($ElapsedTimeTest3.ToString())" #endregion ############################################################################################################################# Write-Host ('*' * 50) Write-Host ("`r`n" * 2) [gc]::Collect() ############################################################################################################################# #region TEST 4: ForEach with direct variable association #---------------------------------------------------------------------------------------------------------------------------- Write-Host 'TEST 4: Testing ForEach with direct variable association' Write-Host "`tAdvantages:" Write-Host "`t- Using ForEach with direct association of output to variable (perfect performance)`r`n" write-host "`tProcessing times:" $stopwatch.Restart() $i = -1 # starting with less 1 as common to show write-progress bar at starting point and not on first hit after 0 #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # This is the execution we're looking at: $testoutput4 = Foreach ($item In $testInput) { #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #....................................................................................................................... #This is just for progress activity returns $i++ If ($i % $progressInterval -eq 0) { write-progress -Activity 'Processing "ForEach with direct variable association"' -PercentComplete($i / $progressMax * 100) If ($i -gt 0) { Write-Host ("`t`t{0,-10} to {1,-10} processed in: {2}" -f ($i - $progressInterval) ,($i) , $($stopwatch.Elapsed - $LastTime)) } $LastTime = $stopwatch.Elapsed } #....................................................................................................................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # This is the execution we're looking at: If ($item % 3 -eq 0) { $item } #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! } $ElapsedTimeTest4 = $stopwatch.Elapsed write-host "`r`n`tTotal time: $($ElapsedTimeTest4.ToString())" #endregion ############################################################################################################################# Write-Host ('*' * 50) Write-Host ("`r`n" * 2) [gc]::Collect() ############################################################################################################################# #region Summary #---------------------------------------------------------------------------------------------------------------------------- write-host ("#" * 70) write-host 'Summary:' write-host ("-" * 70) Write-Host '' $BestExecutionTime = $ElapsedTimeTest1 , $ElapsedTimeTest2 , $ElapsedTimeTest3 , $ElapsedTimeTest4 | Measure-Object -Minimum write-host 'Test1:' $result = [math]::Round($ElapsedTimeTest1.Ticks / $BestExecutionTime.Minimum.Ticks , 2) - 1 If ($result -eq 0) { write-host "`t*** BEST ***" } Else { write-host "`t$result times slower than best" } Write-Host "`r`n" write-host 'Test2:' $result = [math]::Round($ElapsedTimeTest2.Ticks / $BestExecutionTime.Minimum.Ticks , 2) - 1 If ($result -eq 0) { write-host "`t*** BEST ***" } Else { write-host "`t$result times slower than best" } Write-Host "`r`n" write-host 'Test3:' $result = [math]::Round($ElapsedTimeTest3.Ticks / $BestExecutionTime.Minimum.Ticks , 2) - 1 If ($result -eq 0) { write-host "`t*** BEST ***" } Else { write-host "`t$result times slower than best" } Write-Host "`r`n" write-host 'Test4:' $result = [math]::Round($ElapsedTimeTest4.Ticks / $BestExecutionTime.Minimum.Ticks , 2) - 1 If ($result -eq 0) { write-host "`t*** BEST ***" } Else { write-host "`t$result times slower than best" } Write-Host "`r`n" write-host ("#" * 70) #endregion #############################################################################################################################
Ergebnis (Performance Beispiel 1): (Zeiten sind im Format: [STUNDE]:[MINUTE]:[SEKUNDE].[MILLISEKUNDE]
Code:TEST 1: Testing Where-Object Disadvantages: - Using Pipeline-Input (medium performance loss compared to the best approach) Processing times: 0 to 20000 processed in: 00:00:00.2148211 20000 to 40000 processed in: 00:00:00.1917197 40000 to 60000 processed in: 00:00:00.1860353 60000 to 80000 processed in: 00:00:00.1786461 80000 to 100000 processed in: 00:00:00.1769503 100000 to 120000 processed in: 00:00:00.1749551 120000 to 140000 processed in: 00:00:00.1798344 140000 to 160000 processed in: 00:00:00.1899303 160000 to 180000 processed in: 00:00:00.1769276 Total time: 00:00:01.8778313 ************************************************** TEST 2: Testing ForEach with op_addition for the immutable array / creating new arrays (most used) Disadvantages: - Using an Array although it's of a fixed size (very huge performance loss compared to the best approach) Processing times: 0 to 20000 processed in: 00:00:00.8225148 20000 to 40000 processed in: 00:00:02.6714964 40000 to 60000 processed in: 00:00:04.4622587 60000 to 80000 processed in: 00:00:06.1615622 80000 to 100000 processed in: 00:00:07.7802078 100000 to 120000 processed in: 00:00:10.0054058 120000 to 140000 processed in: 00:00:11.9739167 140000 to 160000 processed in: 00:00:14.0041214 160000 to 180000 processed in: 00:00:14.9395773 Total time: 00:01:29.0301052 ************************************************** TEST 3: Testing ForEach with ArrayList Advantages: - Using ArrayList (very low performance loss compared to the best approach) Processing times: 0 to 20000 processed in: 00:00:00.0498273 20000 to 40000 processed in: 00:00:00.0398220 40000 to 60000 processed in: 00:00:00.0418950 60000 to 80000 processed in: 00:00:00.0420203 80000 to 100000 processed in: 00:00:00.0402917 100000 to 120000 processed in: 00:00:00.0396671 120000 to 140000 processed in: 00:00:00.0382014 140000 to 160000 processed in: 00:00:00.0381381 160000 to 180000 processed in: 00:00:00.0404453 Total time: 00:00:00.4241538 ************************************************** TEST 4: Testing ForEach with direct variable association Advantages: - Using ForEach with direct association of output to variable (perfect performance) Processing times: 0 to 20000 processed in: 00:00:00.0484032 20000 to 40000 processed in: 00:00:00.0378398 40000 to 60000 processed in: 00:00:00.0369516 60000 to 80000 processed in: 00:00:00.0390416 80000 to 100000 processed in: 00:00:00.0380139 100000 to 120000 processed in: 00:00:00.0377628 120000 to 140000 processed in: 00:00:00.0382655 140000 to 160000 processed in: 00:00:00.0366415 160000 to 180000 processed in: 00:00:00.0375807 Total time: 00:00:00.3996698 ************************************************** ###################################################################### Summary: ---------------------------------------------------------------------- Test1: 3.7 times slower than best Test2: 221.76 times slower than best Test3: 0.0600000000000001 times slower than best Test4: *** BEST *** ######################################################################
Wie ihr seht, dauert die Ausführung durch die allseits bekannte Variante $Array+=$neuesItem im Beispiel ingesamt mehr als 200-mal länger, als wenn man ForEach in Verbindung mit einer direkten Zuweisung zu einer Variablen verwendet oder alternativ auf eine ArrayList ausweicht, ohne jetzt weiter auf die für Programmierer bessere Variante GenericList mit Typsierung einzugehen
(Derjenige, der die Generic List "[System.Collections.Generic.List[<Type>]]" kennt, ist bei diesem Performance-Tipp eh falsch.)
Hinweis:
Wenn ihr die Variante mit der ArrayList verwenden wollt, dann beim Hinzufügen von neuen Werten immer ein [void] vorne dranhängen (wie oben im Beispiel), da ihr sonst fortlaufend in der Konsole die aktuell hinzugefügte ID ausgebt, was mitunter auch wieder Performance-Probleme verursacht.
- Schreibt nicht via Out-File oder Set-Content über die Pipeline in Dateien!
--> Verwendent eine vorgelagerte Konvertierung zum Typ "single" String (Siehe Beispiel unten])
Variablen oder die Ausgabe von CmdLets, Funktionen, etc direkt in eine Datei zu schreiben, in dem man die Pipeline nutzt, verursacht enorme Performance-Einbußen.
Hintergrund ist, dass beim Pipeline-Input für jede "Zeile" des zugeführten Objekts folgende Operationen auf der Festplatte passieren:- Öffnen
- Schreiben
- Schließen
Das passiert durch das "Stream"-ing.
Um das zu verhindern ist es zu empfehlen die finale Ausgabe erstmal in ein vollständiges Objekt vom Typ String zu konvertieren.
Hier hängt es jedoch stark davon ab, was man am Ende denn in der Datei haben möchte
... simpler Text .... CSV .... XML ???
Egal was auch erreicht werden möchte, die Ausgabe in die Datei sollte nicht über die Pipeline erfolgen, sonst passiert der oben beschriebene Prozess.
Ausnahmen bestätigen wie immer aber auch die Regeln:
Kritische Logs sollten von dieser Variante ausgenommen werden, da es mit meiner Variante natürlich keine "Zwischenstände" gibt, sollte ein System mal abrauchen, obwohl die Datei noch nicht fertig geschrieben wurde.
Performance Beispiel 2:
PowerShell:$testInput = 1 .. 2000000 $stopwatch = [System.Diagnostics.Stopwatch]::new() CLS ############################################################################################################################# #region Initialize functions for the test #---------------------------------------------------------------------------------------------------------------------------- # ConvertTo-String is just for outputting a single text of objects # When outputting delimited content with multiple properties, 'ConvertTo-CSV' has to be used! # Hint: 'ConvertTo-CSV is much faster than 'Out-String" but can't be used for objects with less than 2 properties! Function ConvertTo-String { # Very much faster than 'Out-String' for outputting just single text (but not objects with more than 1 properties!)) ;Use: $out = "ConvertTo-String -InputObject $test" and not the pipeline [CmdletBinding()] Param ( [Parameter(Mandatory = $true , ValueFromPipeline = $true , Position = 0)] $InputObject ) Begin { $StringBuilder = [System.Text.StringBuilder]::new() } Process { Foreach ($item In $InputObject) { [void] $StringBuilder.AppendLine($item -join "`t") } } End { $StringBuilder.ToString() } } Function Get-RandomTextFilePath { Do { $RandomTextFilePath = "$($env:Temp)\$([System.IO.Path]::GetRandomFileName()).txt" } Until (-not(Test-Path $RandomTextFilePath -PathType Leaf)) $RandomTextFilePath } ############################################################################################################################# [gc]::Collect() ############################################################################################################################# #region TEST 1: Set-Content with Pipeline-Input #---------------------------------------------------------------------------------------------------------------------------- $TestFilePath = Get-RandomTextFilePath Write-Host 'TEST 1: Testing Set-Content with Pipeline-Input' Write-Host "`tOutput file path: $TestFilePath" $stopwatch.Start() #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # This is the execution we're looking at: $testInput | Set-Content -Path $TestFilePath -Encoding UTF8 -Force #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! $ElapsedTimeTest1 = $stopwatch.Elapsed write-host "`r`n`tTotal time: $($ElapsedTimeTest1.ToString())" ############################################################################################################################# Write-Host ('*' * 50) Write-Host ("`r`n" * 2) [gc]::Collect() ############################################################################################################################# #region TEST 2: Set-Content withOUT Pipeline-Input #---------------------------------------------------------------------------------------------------------------------------- $TestFilePath = Get-RandomTextFilePath Write-Host 'TEST 2: Testing Set-Content withOUT Pipeline-Input' Write-Host "`tOutput file path: $TestFilePath" $stopwatch.Restart() #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # This is the execution we're looking at: $outString = ConvertTo-String $testInput # Converting the INT array to string with custom function Set-Content -Value $outString -Path $TestFilePath -Encoding UTF8 -Force #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! $ElapsedTimeTest2 = $stopwatch.Elapsed write-host "`r`n`tTotal time: $($ElapsedTimeTest2.ToString())" ############################################################################################################################# Write-Host ('*' * 50) Write-Host ("`r`n" * 2) ############################################################################################################################# #region Summary #---------------------------------------------------------------------------------------------------------------------------- write-host ("#" * 70) write-host 'Summary:' write-host ("-" * 70) Write-Host '' $BestExecutionTime = $ElapsedTimeTest1 , $ElapsedTimeTest2 | Measure-Object -Minimum write-host 'Test1:' $result = [math]::Round($ElapsedTimeTest1.Ticks / $BestExecutionTime.Minimum.Ticks , 2) - 1 If ($result -eq 0) { write-host "`t*** BEST ***" } Else { write-host "`t$result times slower than best" } Write-Host "`r`n" write-host 'Test2:' $result = [math]::Round($ElapsedTimeTest2.Ticks / $BestExecutionTime.Minimum.Ticks , 2) - 1 If ($result -eq 0) { write-host "`t*** BEST ***" } Else { write-host "`t$result times slower than best" } Write-Host "`r`n" write-host ("#" * 70) #############################################################################################################################
Ergebnis (Performance Beispiel 2): (Zeiten sind im Format: [STUNDE]:[MINUTE]:[SEKUNDE].[MILLISEKUNDE]
Code:TEST 1: Testing Set-Content with Pipeline-Input Output file path: C:\Users\scht33v\AppData\Local\Temp\eh2nobam.rcc.txt Total time: 00:00:18.4654221 ************************************************** TEST 2: Testing Set-Content withOUT Pipeline-Input Output file path: C:\Users\scht33v\AppData\Local\Temp\ekrquafk.amd.txt Total time: 00:00:00.8063365 ************************************************** ###################################################################### Summary: ---------------------------------------------------------------------- Test1: 21.9 times slower than best Test2: *** BEST *** ######################################################################
Großer Dank für Anmerkungen, weitere Verbesserungen, Korrekturen, Ideen und Co. geht an:
@RalphS
Zuletzt bearbeitet: