Wo Sie nachsehen sollten, wenn ein PowerShell-Skript langsam ist — Arrays, Pipelines und Abgleich
· Go Komura · PowerShell, Windows, Leistung, Automatisierung, Skript, Betriebsoptimierung, Tuning, Datenverarbeitung
„Das Aggregationsskript war früher in wenigen Minuten fertig; jetzt sind die Daten gewachsen, und es dauert drei Stunden.“ Diese Geschichte hört man häufig an Orten, an denen sich die Automatisierung mit PowerShell etabliert hat. Und in den meisten Fällen liegt die Ursache der Langsamkeit nicht in der rohen Ausführungsgeschwindigkeit von PowerShell, sondern in der Art, wie der Code geschrieben ist.
PowerShell ist eine Sprache, die Schreibfreundlichkeit priorisiert, daher enthält sie zahlreiche Schreibweisen, bei denen „der naheliegende, unmittelbare Ansatz zufällig eine miserable Komplexität hat“. Das Paradebeispiel ist $array += $item. Es sieht natürlich aus, aber intern läuft jedes Mal eine vollständige Kopie des Arrays, und es wird mit wachsender Elementanzahl stark langsamer. Ob man diese klassischen Fallstricke kennt oder nicht, entscheidet darüber, ob dieselbe Aufgabe Stunden oder nur zehn Sekunden dauert.
Dieser Artikel ordnet die Stellen, die Sie bei einem langsamen Skript zuerst verdächtigen sollten, nach ihrer Wirkung geordnet. Ebenso behandelt er, wie man richtig misst, damit Sie nicht aufgrund bloßer Vermutungen umschreiben.
1. Das Wichtigste zuerst
- Verwenden Sie nicht
$array += $item. PowerShell-Arrays haben eine feste Länge, und+=erstellt jedes Mal ein neues Array und kopiert alle Elemente. Es wird proportional zum Quadrat der Elementanzahl langsamer.1 - Verwenden Sie stattdessen
List[T], oder weisen Sie die Ausgabe der gesamten Schleife einer Variablen zu. Letzteres ist idiomatisches PowerShell und schnell. - Für Abgleich und Suche bringen Hashtabellen den größten Gewinn. Eine lineare Suche in einer verschachtelten Schleife (O(n×m)) wird zu einem Schlüsselzugriff (grob O(n+m)).2
- Die Pipeline ist bequem, hat aber Kosten pro Objekt. In inneren Schleifen mit großen Mengen ist die
foreach-Anweisung oft schneller, und es lohnt sich, das zu messen.3 - Wählen Sie die Methode zum Lesen von Dateien nach Zweck.
Get-Content -Rawliest alles auf einmal;-ReadCountliest in Stapeln. Manchmal ist es die Objekterzeugung pro Zeile, die Ihnen schadet.4 - Verwenden Sie
-FiltermitGet-ChildItem. Da die Filterung auf Provider-Seite erfolgt, ist dies effizienter, als alles abzurufen und mitWhere-Objectzu verwerfen — das sagt die offizielle Dokumentation ausdrücklich.5 - Verketten Sie Zeichenketten mit
-joinoderStringBuilder.$s += "..."ist aus demselben Grund wie bei Arrays langsam.6 Format-*gehört nur ganz ans Ende, zur Anzeige. Setzen Sie es dazwischen, brechen Sie sowohl die nachgelagerte Verarbeitung als auch verursachen unnötige Formatierungskosten.7- Erst messen, dann korrigieren.
Measure-Commandverwirft die Ausgabe, enthält also nicht die Anzeigekosten. Verwenden Sie niemals den Wert des ersten Durchlaufs.8 - Parallelisierung kommt zuletzt. Erwägen Sie sie erst, nachdem Sie den Algorithmus korrigiert haben (siehe „Parallele Verarbeitung in PowerShell“).
2. Zuerst messen — Measure-Command richtig verwenden
Wer nach Vermutung tunt, korrigiert meist eine Stelle, die nichts bewirkt. Als Erstes ist zu messen.8
# Der erste Durchlauf enthält Modul-Ladezeit und JIT, daher verwerfen
$null = Measure-Command { Invoke-KsAggregate }
# Den zweiten und weitere Durchläufe mehrmals messen und auch die Streuung betrachten
1..3 | ForEach-Object {
(Measure-Command { Invoke-KsAggregate }).TotalSeconds
}
Bei Measure-Command sind zwei Dinge zu beachten.
- Die Ausgabe des Skriptblocks wird verworfen. Die Kosten für Formatierung und Bildschirmzeichnung sind nicht enthalten. Ist die gefühlte Langsamkeit beim tatsächlichen Lauf spürbar, liegt der Übeltäter manchmal auf der Anzeigeseite
- Unter denselben Bedingungen vergleichen. Ob der Dateicache warm ist, macht bei Messungen mit E/A-Anteil einen großen Unterschied
Wenn Sie herausfinden möchten, welcher Teil der Verarbeitung langsam ist, verwenden Sie Stopwatch.
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$rows = Import-Csv $csvPath
Write-Verbose "Load: $($sw.ElapsedMilliseconds) ms"; $sw.Restart()
$index = $master | Group-Object Code -AsHashTable -AsString
Write-Verbose "Build index: $($sw.ElapsedMilliseconds) ms"; $sw.Restart()
$result = foreach ($r in $rows) { Join-KsRecord $r $index }
Write-Verbose "Match: $($sw.ElapsedMilliseconds) ms"; $sw.Stop()
Geben Sie Zeiten auf diese Weise abschnittsweise aus, werden Tatsachen wie „das Laden machte tatsächlich 80 % aus“ sofort ersichtlich. Write-Verbose wird hier verwendet, damit im normalen Lauf nichts erscheint und es nur während einer Untersuchung sichtbar ist (siehe „PowerShells Ausgabeströme und Log-Design“).
3. Der größte Übeltäter — += bei Arrays
PowerShell-Arrays haben eine feste Länge. Sie können ihnen kein Element hinzufügen, und += entfaltet sich zu „ein neues Array erstellen, jedes Element hineinkopieren und das neue Element am Ende hinzufügen“.1 Mit anderen Worten führt eine Schleife, die n Elemente hinzufügt, insgesamt eine zum Quadrat von n proportionale Kopiermenge aus.
# [SCHLECHT] wird mit wachsender Elementanzahl drastisch langsamer
$result = @()
foreach ($row in $rows) {
$result += ConvertTo-KsRecord $row # jedes Mal wird das gesamte Array kopiert
}
# [GUT-1] List[T] verwenden (Hinzufügen eines Elements ist konstante Zeit)
$result = [System.Collections.Generic.List[object]]::new()
foreach ($row in $rows) {
$result.Add((ConvertTo-KsRecord $row))
}
# [GUT-2] die Ausgabe der gesamten Schleife einer Variablen zuweisen (idiomatisches PowerShell, und schnell)
$result = foreach ($row in $rows) {
ConvertTo-KsRecord $row # die Ausgabe wird unverändert gesammelt
}
Hat man sich erst daran gewöhnt, ist die Schreibweise [GUT-2] die natürlichste Form in PowerShell. Die Ausgabe einer foreach-Anweisung, von ForEach-Object, if und Ähnlichem lässt sich direkt in einer Variablen sammeln. Diese Eigenschaft geht Hand in Hand mit dem Verständnis der „Ausgabeströme“.
Dieselbe Logik gilt für Zeichenketten. Zeichenketten sind unveränderlich (immutable), daher erstellt $s += "line" jedes Mal eine neue Zeichenkette.
# [SCHLECHT]
$text = ''
foreach ($line in $lines) { $text += "$line`r`n" }
# [GUT-1] -join (am prägnantesten)
$text = $lines -join "`r`n"
# [GUT-2] StringBuilder (für komplexe Zusammensetzung mit Bedingungen)
$sb = [System.Text.StringBuilder]::new()
foreach ($line in $lines) { [void]$sb.AppendLine($line) }
$text = $sb.ToString()
4. Verschachtelte Schleifen loswerden — Abgleich mit einer Hashtabelle
Als Nächstes an Wirkung folgt der Abgleich (Matching). Schreibt man „für jede Zeile der Bestelldaten den Produktnamen aus dem Stamm nachschlagen“ naheliegend, erhält man Folgendes.
# [SCHLECHT] 10.000 Bestellungen x 10.000 Stammzeilen = 100 Millionen Vergleiche
foreach ($order in $orders) {
$item = $master | Where-Object { $_.Code -eq $order.Code } # jedes Mal ein vollständiger Durchlauf
$order | Add-Member NoteProperty ItemName $item.Name
}
Wechselt man zu einer Hashtabelle, sinkt die Anzahl der Vergleiche drastisch.2
# [GUT] den Index einmal erstellen, danach nur per Schlüssel nachschlagen
$index = $master | Group-Object -Property Code -AsHashTable -AsString
$result = foreach ($order in $orders) {
$hit = $index[$order.Code] # der Schlüsselzugriff hängt nicht von der Elementanzahl ab
[pscustomobject]@{
Code = $order.Code
Qty = $order.Qty
ItemName = if ($hit) { $hit[0].Name } else { $null } # nicht registrierte Codes lassen sich auch erkennen
}
}
Group-Object -AsHashTable fasst Elemente mit demselben Schlüssel in einem Array zusammen, daher sind die Werte Arrays (daher oben $hit[0]). Wissen Sie, dass der Schlüssel eindeutig ist, spricht nichts dagegen, die Hashtabelle selbst zu erstellen.
$index = @{}
foreach ($m in $master) { $index[$m.Code] = $m } # setzt eindeutige Schlüssel voraus
Wenn Sie nur wiederholt prüfen müssen, „ist es enthalten?“, ist ein HashSet wirkungsvoll. -contains und -in gegen ein Array sind lineare Suchen, sodass sie bei einer hohen Anzahl von Prüfungen spürbar werden.
$known = [System.Collections.Generic.HashSet[string]]::new(
[string[]]$master.Code, [System.StringComparer]::OrdinalIgnoreCase)
$unknown = $orders | Where-Object { -not $known.Contains($_.Code) }
Praktische Muster für den Abgleich von CSV-Dateien gegeneinander werden auch in „Excel- und CSV-Geschäftsverarbeitung mit PowerShell automatisieren“ behandelt.
5. Pipeline gegenüber der foreach-Anweisung
Die Pipeline ist eine zentrale Funktion von PowerShell, aber es gibt Kosten pro Objekt beim Durchfließen zwischen Cmdlets. In inneren Schleifen im Umfang von Hunderttausenden Elementen ist die foreach-Anweisung oft schneller.3
# Pipeline: lesbar, und verarbeitet sequenziell, sodass keine Zwischenergebnisse angesammelt werden
$rows | Where-Object { $_.Status -eq 'OK' } | ForEach-Object { $_.Amount } |
Measure-Object -Sum
# foreach-Anweisung: geringerer Overhead pro Element, schneller bei großen Mengen
$sum = 0
foreach ($r in $rows) { if ($r.Status -eq 'OK') { $sum += $r.Amount } }
Der Speicher wird leicht missverstanden, daher eine Anmerkung dazu. Die foreach-Anweisung wertet zuerst den Ausdruck in den Klammern aus und beginnt dann mit der Schleife. Übergeben Sie also das Ergebnis eines Befehls — wie in foreach ($line in (Get-Content $path)) —, landen zu diesem Zeitpunkt alle Elemente im Speicher.3 Übergeben Sie hingegen ein lazy aufzählendes IEnumerable, werden die Elemente einzeln aufgezählt. Selbst bei einer riesigen Datei lässt sich daher, wie folgt geschrieben, weiterhin die foreach-Anweisung verwenden, ohne Speicher zu verbrauchen.
# Mit der foreach-Anweisung verarbeiten, ohne alles in den Speicher zu laden (verzögerte Aufzählung)
foreach ($line in [System.IO.File]::ReadLines($path)) {
if ($line.StartsWith('ERROR')) { $errors++ }
}
Achten Sie jedoch darauf, dass sich die standardmäßige Zeichenkodierung unterscheidet. Das einargumentige ReadLines($path) liest als UTF-8 (bevorzugt ein BOM, falls vorhanden). Das Get-Content von Windows PowerShell 5.1 hingegen liest, wenn kein BOM vorhanden ist, mit der aktuellen ANSI-Codepage (Shift_JIS in einer japanischen Umgebung, in einer deutschen Umgebung typischerweise Windows-1252). Ersetzt man ein Shift_JIS-Protokoll ohne Weiteres durch diese Variante, entstehen also Darstellungsfehler. Verwenden Sie beim Austausch die Überladung, die die Kodierung explizit angibt.
# Beim Lesen eines Shift_JIS-(CP932-)Protokolls
# Ab PowerShell 6.2 ist der Codepage-Provider bereits registriert,
# sodass GetEncoding(932) direkt aufgerufen werden kann (ebenso unter Windows PowerShell 5.1)
$enc = [System.Text.Encoding]::GetEncoding(932)
foreach ($line in [System.IO.File]::ReadLines($path, $enc)) {
if ($line.StartsWith('ERROR')) { $errors++ }
}
Beachten Sie, dass in PowerShell 7 der Standard für Get-Content UTF-8 (ohne BOM) ist, sodass die Ergebnisse übereinstimmen, solange Sie UTF-8-Dateien unter 7 verarbeiten, auch mit der einargumentigen Überladung. Prüfen Sie vor dem Austausch stets die Zeichenkodierung der Zieldateien und die Version der Ausführungsumgebung.
Die Faustregeln sind wie folgt.
| Situation | Wahl |
|---|---|
| Wenige Elemente / Lesbarkeit hat Vorrang | Pipeline |
Riesige Eingabe, übergeben als Befehlsergebnis (Get-Content usw.) |
Pipeline, oder [IO.File]::ReadLines + foreach-Anweisung |
| Iteration über Hunderttausende bereits im Speicher befindliche Elemente | foreach-Anweisung |
| Einfache Filterung über ein Array | Methoden .Where({...}) / .ForEach({...})1 |
.Where() und .ForEach() sind in PowerShell 4.0 hinzugefügte Array-Methoden, und sie sind leichter, da sie die Pipeline umgehen.1 Sie setzen jedoch voraus, dass das Ziel bereits eine Sammlung im Speicher ist.
6. Datei- und Verzeichnis-E/A
Wählen Sie beim Lesen je nach Zweck. Get-Content erzeugt standardmäßig pro Zeile ein Objekt, sodass bei riesigen Dateien diese Kosten dominieren.4
Get-Content $path # zeilenweise (ein Objekt pro Zeile)
Get-Content $path -Raw # alles auf einmal als eine einzige Zeichenkette lesen
Get-Content $path -ReadCount 1000 # Arrays mit 1000 Zeilen übergeben (weniger Objekterzeugung)
[System.IO.File]::ReadLines($path) # .NET. Sequenzielle Aufzählung, am leichtesten (Standard UTF-8)
Verwenden Sie für das Durchlaufen von Verzeichnissen -Filter. Die offizielle Dokumentation stellt ausdrücklich fest, dass „Filter effizienter sind als andere Parameter, da der Provider sie beim Abrufen der Objekte anwendet“.5
# [SCHLECHT] alles abrufen und dann den Großteil verwerfen
Get-ChildItem -Path $root -Recurse | Where-Object { $_.Extension -eq '.log' }
# [GUT] auf Provider-Seite eingrenzen
Get-ChildItem -Path $root -Recurse -File -Filter '*.log'
# Ist es bei Hunderttausenden von Dateien immer noch langsam, .NET-Aufzählung erwägen
[System.IO.Directory]::EnumerateFiles($root, '*.log', 'AllDirectories')
Beim Schreiben ist das Anhängen bei jeder Iteration innerhalb einer Schleife der klassische Engpass. Add-Content öffnet und schließt die Datei bei jedem Aufruf.
# [SCHLECHT] 10.000 Zeilen = 10.000 Öffnen-/Schließen-Zyklen
foreach ($r in $result) { Add-Content -Path $out -Value ($r -join ',') }
# [GUT-1] alles in einem Rutsch schreiben
$result | ForEach-Object { $_ -join ',' } | Set-Content -Path $out -Encoding utf8
# [GUT-2] wenn tatsächlich inkrementelles Schreiben nötig ist, einen StreamWriter offen halten
$writer = [System.IO.StreamWriter]::new($out, $false, [System.Text.UTF8Encoding]::new($false))
try { foreach ($r in $result) { $writer.WriteLine($r -join ',') } }
finally { $writer.Dispose() }
Bei Dateioperationen über das Netzwerk dominiert von vornherein die schiere Anzahl der Roundtrips. Die Besonderheiten von UNC-Pfaden werden in „Fallstricke von Netzlaufwerken und UNC-Pfaden“ behandelt.
7. Kleine, leicht übersehene Kosten
- Die Aktualisierungshäufigkeit von
Write-Progress. Aktualisieren Sie den Fortschritt bei jeder Iteration, kann der Zeichenaufwand die Verarbeitungszeit übersteigen. Dünnen Sie aus — etwa alle 100 Elemente — oder stoppen Sie ihn bei unbeaufsichtigter Ausführung ganz mit$ProgressPreference = 'SilentlyContinue' Format-Table/Format-Listin der Mitte einfügen. Sie werden in Formatierungsobjekte umgewandelt, was sowohl die nachgelagerte Verarbeitung bricht als auch unnötig Kosten verursacht. Formatieren Sie nur am Ende7- Unveränderliche Arbeit innerhalb von Schleifen. Allein das Herausziehen von Dingen wie der Konstruktion von
Get-Date-Formatstrings, der Kompilierung regulärer Ausdrücke und dem Neuladen von Modulen aus der Schleife kann sich auszahlen - Übermäßiger Gebrauch von
Select-Object -Property. Es erzeugt ein neues PSCustomObject, was bei hohen Mengen alles andere als vernachlässigbar ist. Das ursprüngliche Objekt bis zu der Stufe zu behalten, an der Sie es tatsächlich benötigen, kann schneller sein - Häufige Ausnahmen. Designs, die riesige Mengen an
try/catch-Durchläufen ausführen (etwa jedes MalGet-Itemfür eine nicht existierende Datei aufrufen und die Ausnahme schlucken), sind teuer. Verzweigen Sie stattdessen vorab mitTest-Path
8. Bewährte Praxis in der Praxis (Entscheidungstabelle)
| Symptom | Wo zu verdächtigen ist | Korrektur |
|---|---|---|
| Plötzlich langsam bei wachsender Elementanzahl | $array += / $string += |
List[T], Zuweisung der Schleifenausgabe an eine Variable, -join1 |
| Der Abgleich zweier Datensätze wird nie fertig | Lineare Suche in einer verschachtelten Schleife | Hashtabelle / Group-Object -AsHashTable2 |
| Das Lesen einer riesigen Datei ist langsam | Get-Content erzeugt ein Objekt pro Zeile |
-Raw / -ReadCount / [File]::ReadLines4 |
| Das Durchlaufen von Ordnern ist langsam | Filterung nachgelagert mit Where-Object |
Get-ChildItem -Filter, bei Bedarf EnumerateFiles5 |
| Das Schreiben der Ausgabedatei ist langsam | Add-Content innerhalb einer Schleife |
Massenschreibung, oder StreamWriter6 |
| Die CPU ist untätig, aber es wird trotzdem nicht fertig | Netzwerk- oder E/A-Wartezeit | Hier verdient sich die Parallelisierung ihren Platz (siehe „Parallele Verarbeitung“) |
| Die Bildschirmanzeige ist langsam | Formatierung und Zeichnen | Format-* nur am Ende, $ProgressPreference deaktivieren7 |
| Keine Ahnung, wo die Zeit hingeht | Zu wenig gemessen | Abschnittsweise Zeitmessung mit Stopwatch, Measure-Command ab dem zweiten Durchlauf8 |
9. Zusammenfassung
- Erst messen. Beachten Sie, dass
Measure-Commanddie Ausgabe verwirft und daher die Anzeigekosten ausschließt, und dass der Wert des ersten Durchlaufs unbrauchbar ist. $array +=wird proportional zum Quadrat der Elementanzahl langsamer. Es durchList[T]oder die Zuweisung der Schleifenausgabe an eine Variable zu ersetzen, hat oberste Priorität.- Abgleiche in Hashtabellen-Zugriffe umzuwandeln, ist die kosteneffektivste verfügbare Verbesserung. Finden Sie eine verschachtelte Schleife, fragen Sie sich, ob Sie einen Index aufbauen können.
- Wählen Sie Lese- und Schreibmethoden für Datei-E/A nach Zweck. Providerseitige Filterung mit
-Filter, Massenschreibvorgänge und zu wissen, wannStreamWritersinnvoll ist, sind die Grundlagen. - Fügen Sie
Format-*nicht in der Mitte ein, dünnen Sie Fortschrittsaktualisierungen aus, ziehen Sie unveränderliche Arbeit aus Schleifen heraus — auch kleine Anhäufungen wirken sich aus, wenn die Mengen groß sind. - Parallelisierung ist das letzte Mittel. Korrigieren Sie zuerst den Algorithmus und wenden Sie sie dann auf Arbeit an, bei der Wartezeit dominiert.
Beispielcode zum Download
Der in diesem Artikel behandelte Code ist in direkt ausführbarer Form gepackt. Er enthält drei Benchmarks und einen Mess-Helfer.
Beispielcode herunterladen (zip)
Die Beispiele dieses Artikels wurden tatsächlich unter PowerShell 7.6 ausgeführt und verifiziert (8 Pester-Tests). Führen Sie das im zip enthaltene Invoke-SampleTests.ps1 aus, um dieselbe Verifikation auf Ihrer eigenen Maschine zu reproduzieren.
# Syntaxprüfung + statische Analyse + Pester-Tests
./Invoke-SampleTests.ps1
Die Konfigurationswerte (Pfade, Servernamen, Mandanten-IDs usw.) sind Beispiele. Führen Sie sie nicht unverändert in einer Produktivumgebung aus — passen Sie sie an Ihre eigene Umgebung an.
Verwandte Artikel
- Parallele Verarbeitung in PowerShell — Die Wahl zwischen ForEach-Object -Parallel und Jobs
- PowerShells Ausgabeströme und Log-Design — Schluss mit Write-Host
- Excel- und CSV-Geschäftsverarbeitung mit PowerShell automatisieren — Praktische Rezepte für Aggregation, Abgleich und Berichtsausgabe
- Praktische PowerShell-Befehlsrezepte — Die kleinen Werkzeuge erweitern, die Sie täglich verwenden
- So vergleichen Sie die Geschwindigkeit verschiedener Programmversionen unter Windows korrekt
- „Langsam“ mit PerfView und dotnet-trace identifizieren
Verwandte Beratungsbereiche
Die KomuraSoft LLC übernimmt die Beschleunigung von zu lange dauernden Aggregations- und Abgleichprozessen, das Umbauen von Verarbeitungsdesigns, damit sie wachsenden Datenmengen standhalten, sowie die Untersuchung von Ursachen für Leistungseinbußen.
- Technische Beratung & Design-Review
- Fehlersuche & Ursachenanalyse
- Migration & Nutzung vorhandener Ressourcen
- Kontakt
Referenzlinks
-
Microsoft Learn, about_Arrays. Dazu, dass PowerShell-Arrays eine feste Größe haben und der Operator += daher ein neues Array erstellt, die vorhandenen Elemente kopiert und dann das neue hinzufügt; dazu, dass Sammlungstypen (wie List) besser geeignet sind, wenn Elemente wiederholt hinzugefügt und entfernt werden; sowie zu den in PowerShell 4.0 hinzugefügten Methoden .Where() und .ForEach(). ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Group-Object. Dazu, dass -AsHashTable das Gruppierungsergebnis als Hashtabelle zurückgibt, dass -AsString die Schlüssel als Zeichenketten behandelt, und dazu, dass die Werte der zurückgegebenen Hashtabelle Arrays der Elemente jeder Gruppe sind. ↩ ↩2 ↩3
-
Microsoft Learn, about_Foreach. Dazu, dass die foreach-Anweisung den Ausdruck innerhalb der Klammern auswertet, bevor die Iteration beginnt — sodass das Übergeben des Ergebnisses eines Befehls dieses Ergebnis im Speicher hält —, während das Cmdlet ForEach-Object seine Eingabe Element für Element aus der Pipeline erhält, sowie zur Wahl zwischen beiden. Als Beispiel für verzögerte Aufzählung erläutert die Methode File.ReadLines, wie sie zeilenweise aufzählt, ohne die gesamte Datei zu lesen (der Unterschied zu ReadAllLines). ↩ ↩2 ↩3
-
Microsoft Learn, Get-Content. Dazu, dass standardmäßig ein Objekt pro durch Zeilenumbrüche getrennter Zeile zurückgegeben wird, dass -Raw die gesamte Datei als eine einzige Zeichenkette liest, und dazu, dass -ReadCount eine angegebene Anzahl von Zeilen auf einmal in die Pipeline sendet. ↩ ↩2 ↩3
-
Microsoft Learn, Get-ChildItem. Dazu, dass -Filter vom Provider beim Abrufen der Objekte angewendet wird und daher effizienter ist als andere Parameter, die nach dem Abruf filtern, sowie zur Kombination mit -File und -Recurse. ↩ ↩2 ↩3
-
Microsoft Learn, StringBuilder-Klasse. Dazu, dass String unveränderlich ist, sodass jede Verkettung eine neue Instanz erzeugt, während StringBuilder die Zeichenkette in einem veränderlichen Puffer zusammensetzt. Ebenso die StreamWriter-Klasse zum inkrementellen Schreiben bei offen gehaltenem Stream. ↩ ↩2
-
Microsoft Learn, Format-Table. Dazu, dass die Format-*-Cmdlets Formatierungsobjekte für die Anzeige erzeugen, wodurch sie sich nicht zum Weiterleiten an nachfolgende Befehle eignen und nur am Ende einer Pipeline angemessen sind. ↩ ↩2 ↩3
-
Microsoft Learn, Measure-Command. Zur Messung der Ausführungszeit eines Skriptblocks oder Befehls unter Rückgabe eines TimeSpan, und dazu, dass die gemessene Ausgabe selbst nicht im Ergebnis enthalten ist. Ebenso die Stopwatch-Klasse zur Messung einzelner Abschnitte. ↩ ↩2 ↩3
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Schluss mit Write-Host — PowerShells Ausgabeströme und Log-Design
Wie Sie zwischen den sechs Ausgabeströmen von PowerShell wählen, welche Probleme Write-Host mit sich bringt und wo es tatsächlich hingehö...
Parallelverarbeitung in PowerShell — Die Wahl zwischen ForEach-Object -Parallel und Jobs
Ein praxisnaher Überblick über die Unterschiede zwischen ForEach-Object -Parallel, Start-ThreadJob und Start-Job und wann welches zum Ein...
Externe EXEs aus PowerShell korrekt aufrufen — Die Fallstricke bei Argument-Quoting, Exitcodes und Zeichensalat
Rufen Sie robocopy oder eine hauseigene EXE aus PowerShell auf, und die Argumente zerbrechen, der Exitcode ist nicht verfügbar, und die A...
Zugangsdaten in PowerShell sicher handhaben — Klartext-Passwörter aus Ihren Skripten verbannen
Ein praktischer Leitfaden, um Klartext-Passwörter aus PowerShell-Skripten zu entfernen und sicher zu speichern: was SecureString wirklich...
Parameterdesign und Modularisierung für PowerShell-Skripte — Von der „funktionierenden“ zur „übergabefähigen“ Skript
Eine schrittweise Vorgehensweise, um ein PowerShell-Skript auf eine Qualität zu heben, die Sie an andere übergeben können. Behandelt den ...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Warum ist die Schreibweise $array += $item langsam?
- Weil PowerShell-Arrays eine feste Länge haben und keine Elemente hinzugefügt werden können. Der Operator += entfaltet sich zu „ein neues Array erstellen, jedes vorhandene Element hineinkopieren und am Ende anhängen“. Bei jedem einzelnen hinzugefügten Element läuft eine vollständige Kopie, sodass das Hinzufügen von n Elementen eine zum Quadrat von n proportionale Zeit kostet. Bei 100 Elementen bemerken Sie es nicht; ab 10.000 wird es spürbar langsam, und bei 100.000 ist es unbrauchbar. Die Lösung ist, System.Collections.Generic.List[T] zu verwenden und Add aufzurufen, oder so umzuschreiben, dass die Ausgabe der gesamten Schleife einer Variablen zugewiesen wird.
- Ich habe mit Measure-Command gemessen, aber es stimmt nicht mit der tatsächlichen Laufzeit überein.
- Weil Measure-Command nur die Ausführungszeit des Skriptblocks misst und dessen Ausgabe verwirft. Beim tatsächlichen Lauf kommen zusätzlich die Kosten für die Formatierung der Ergebnisse zur Anzeige sowie die Zeit für das Zeichnen auf der Konsole hinzu. Außerdem enthält der erste Durchlauf das Laden von Modulen und die JIT-Kompilierung, sodass der erste Messwert meist unzuverlässig ist. Halten Sie sich an zwei Regeln: dieselbe Operation mehrmals ausführen und die Werte ab dem zweiten Durchlauf betrachten, und Vergleichsobjekte stets unter denselben Bedingungen messen.
- Der Abgleich zweier CSV-Dateien wird nie fertig. Was sollte ich korrigieren?
- Es ist sehr wahrscheinlich, dass Sie in der inneren Schleife jedes Mal eine lineare Suche mit Where-Object durchführen. Hat jede Datei 10.000 Zeilen, ergibt das 100 Millionen Vergleiche. Wandeln Sie eine Seite in eine Hashtabelle um (oder verwenden Sie Group-Object -AsHashTable) und schlagen Sie über den Schlüssel nach, dann sinkt die Anzahl der Vergleiche auf ungefähr die Summe beider Zeilenzahlen. Der Effekt wächst mit den Datenmengen — dies ist die kosteneffektivste verfügbare Verbesserung.
- Ich habe gehört, dass der direkte Aufruf von .NET-APIs schneller ist als die Verwendung von Cmdlets. Sollte ich das immer tun?
- Nein — es ist eine Abwägung. .NET-APIs (etwa ReadLines oder EnumerateFiles von System.IO.File) sind tatsächlich schneller, aber Sie verlieren die Bequemlichkeit der Provider-Funktionen von PowerShell, Platzhalter und die Auflösung relativer Pfade, und der Code wird schwerer lesbar. Messen Sie zuerst und ersetzen Sie nur die Stellen, die sich tatsächlich als Engpass erwiesen haben. Praktisch ist es, dies auf Situationen zu beschränken, in denen es eindeutig lohnt, etwa beim Durchlaufen Hunderttausender Dateien oder beim Lesen riesiger Dateien.
- Wird die Parallelisierung der Verarbeitung sie beschleunigen?
- Sie hilft, wenn Wartezeit die Verarbeitung dominiert, aber in der Reihenfolge kommt sie zuletzt. Reduzieren Sie zuerst die verschwendete Arbeit: grenzen Sie die abgerufene Menge ein, ziehen Sie unveränderliche Arbeit aus Schleifen heraus, und wandeln Sie Abgleiche in Hash-Zugriffe um. Parallelisieren Sie einen Algorithmus, der noch O(n²) ist, gewinnen Sie nur eine zur Kernanzahl proportionale Beschleunigung. Umgekehrt kann das Parallelisieren von Arbeit, bei der jedes Element leichtgewichtig ist, durch den Overhead sogar langsamer werden.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.