PowerShell – FileWritePerformance (StreamWriter)

In meinem letzten Script habe ich eine Textdatei mit 100.000 Zeilen erstellen wollen, um die StreamReader-Performance in der PowerShell zu demonstrieren. Das hat sehr lange gedauert. Aber wenn der StreamReader so schnell Dateien LESEN kann – wie schnell kann dann der StreamWriter SCHREIBEN?

Für den Vergleich habe ich mehrere Variationen zusammengestellt. Alle sollen eine Datei mit der gleichen Anzahl an Zeilen und dem gleichen Text erstellen. Jede Variante habe ich mit Measure-Command gemessen. Der Vergleich ist überraschend.

1. Versuchsreihe

Die Parameter sind einfach:

PowerShell – FileWritePerformance (StreamWriter)

Variante 1 ist ein Output-Operator mit einer Mengenschleife:

PowerShell – FileWritePerformance (StreamWriter)

Variante 2a ist eine Mengenschleife gepipt gegen Out-File:

PowerShell – FileWritePerformance (StreamWriter)

In Variante 2b joine ich die Zeilen vor dem Out-File in einen einzelnen String:

PowerShell – FileWritePerformance (StreamWriter)

Vielleicht ist ein Export-CSV in Variante 3 schneller?

PowerShell – FileWritePerformance (StreamWriter)

Oder ist die Variante 4 – der .net StreamWriter – der Spitzenreiter?

PowerShell – FileWritePerformance (StreamWriter)

Mit Measure-Object kann ich die Laufzeiten in Sekunden vergleichen. Dazu verpacke ich jede Variante in einen solchen Block:

PowerShell – FileWritePerformance (StreamWriter)

Das Ergebnis ist überraschend:

PowerShell – FileWritePerformance (StreamWriter)

Der StreamWriter ist nicht schneller als das Pipen eines großen Strings! Das ist erstaunlich, da ein StreamReader viel schneller ließt als ein cmdlet.

2. Versuchsreihe

Liegt es vielleicht nicht am Schreiben, sondern an der Generierung der Ausgabemenge? Das lässt sich leicht mit einer Variablen testen:

PowerShell – FileWritePerformance (StreamWriter)

Die unterschiedlichen Varianten sehen damit so aus:

PowerShell – FileWritePerformance (StreamWriter)

Bei dem StreamWriter wird nun nicht mehr jede Zeile einzeln geschrieben. Daher verwende ich die Methode Write statt WriteLine.

Und was sagt nun Measure-Object dazu? Um den Einfluss der Datenerzeugung zu visualisieren habe ich auch diese Anweisung gemessen:

PowerShell – FileWritePerformance (StreamWriter)

Jetzt kommen wir der Sache näher:

  • Das Erstellen der Datenzeilen im RAM hat einen großen Teil der gesamten Verarbeitungszeit beansprucht.
  • Man erkennt deutlich, dass ein gepiptes Out-File und ein „>>“-Operator das Gleiche sind.
  • Ebenso erkennt man, dass die Umleitung eines (gejointen) Strings mit Out-File als Stream behandelt wird. Aber dennoch ist der StreamWriter viel effizienter!
  • Export-CSV benötigt für die Konvertierung mehr Rechenzeit.

Das Streamen lohnt sich also, wenn es richtig angewandt wird!

Optimierung der String-Generierung

Kann man am Erstellen der Demo-Daten vielleicht auch noch etwas optimieren? Offenbar ist das Foreach-Object und die Pipeline nicht performance-optimiert.

Die Textvervielfältigung kann durch den Operator „*“ erreicht werden. „Text mal Zahl“ ergibt ein Vielfaches des Textes:

PowerShell – FileWritePerformance (StreamWriter)

Nur fehlt hier der Zeilenumbruch. Den kann ich aber vorher integrieren:

PowerShell – FileWritePerformance (StreamWriter)

Wie lange dauert es, bis so die gleiche Anzahl an Zeilen in einer Variable steht?

PowerShell – FileWritePerformance (StreamWriter)

Statt fast einer Minute habe ich die 100.000 Zeilen in 43 Millisekunden im RAM erzeugt!

Das Finale ist dieser Code: er erzeugt eine Demodatei mit 100.000 Zeilen (gemessen von Measure-Command):

PowerShell – FileWritePerformance (StreamWriter)

PowerShell – FileWritePerformance (StreamWriter)

Schneller geht es nicht! Das übertraf meine Erwartungen!!!

Ein Paar Experimente mit dem CLI-Verhalten der PowerShell können sich schnell lohnen! Falls ihr die FileWritePerformance selber einman ansehen wollt – hier gibt es den Code zum Download: Show-FileWritePerformance

Stay tuned!