Powershell Where-Object mit Warp 7 beschleunigen
Powershell Where-Object mit Warp 7 beschleunigen

Es gibt verschiedene Möglichkeiten wie man mit Powershell zum Ziel kommt. Bei einem Projekt musste ich mehrere CSV Tabellen vergleichen die jeweils mit Primären und Fremdschlüssel verbunden waren. Hinzu kam, das Informationsfelder mitgenommen werden mussten und es auch eine 1..n Beziehung beinhaltete. Somit viel die Hashtable (Key, Value) aussen vor. Das Script hatte mit der einfachen Where-Object Funktion eine Laufzeit von ca. 1 Stunde.

Mit einem Trick beschleunigte ich das Script um das Mehrfache. Der Clou bei der Geschichte war dabei, das man anstatt das Reguläre Where-Object ein .NET Framework Objekt erzeugt. Dadurch rannte das Script Sprichwörtlich in 5 Minuten durch. 

Testdaten

Zum testen brauchen wir zuerst mal Daten. Diese können wir mit Powershell relativ einfach generieren. Wir erstellen eine CSV mit Private Key und einer Zufälligen GUID. Aus den Daten entnehmen wir einen Eintrag in die Variable $pkid und fügen diesen Wert noch zum Schluss ein Damit wir einen Mehrfach wert zurück erhalten.

'"no";"guid"'> .\guid.csv
0..5000|foreach{ '"'+$_.ToString().PadLeft(5,"0")+'";"'+[guid]::NewGuid().guid+'"'>>.\guid.csv }
$guid = Import-CSV .\guid.csv -Delimiter ";" 
$pkid = $guid[989].guid
'"05001";"'+$pkid+'"'>> .\guid.csv
$guid = Import-CSV .\guid.csv -Delimiter ";"  
$pkid2 = $guid[5001].guid

Die "Native" Powershell

Im Normalfall reicht dieser Weg, den sind es nur ein paar 100 Einträge ist dieser Weg genug schnell. Wir Iterieren mit dem Where-Object und Suchen nach dem Wert den wir in der Variable $pkid haben. 

Write-Host " -----TIME Second Powershell Nativ" -ForegroundColor Yellow 
$timer = [System.Diagnostics.Stopwatch]::StartNew()
$guid | Where-Object{ $_."guid"-eq $pkid} | Format-Table
$timer.Stop()
$timer.ElapsedMilliseconds

$timer = [System.Diagnostics.Stopwatch]::StartNew()
$report = $guid | % { if ($_.guid -eq $pkid) { [PSCustomobject]@{no = $r.no;guid = $_.guid }}}
$report | ft
$timer.Stop()
$timer.ElapsedMilliseconds

In ~104 Millisekunden ist der Wert gefunden. Das Resultat direkt in die Variable $report zu schreiben verkürzt die Zeit nochmals um ~30 Milisekunden.

-----TIME Second Powershell Nativ
no    guid
--    ----
00989 86590a7d-5c13-4454-b40e-2bad59a5b605

104

no    guid
--    ----
00989 86590a7d-5c13-4454-b40e-2bad59a5b605

73

Der kleine Umweg über C#

In Powershell hat man die Möglichkeit auf das ganze Dot.Net Ökosystem zuzugreifen. Dabei konvertieren wir das PSCustomObject zu einer .NET Framework Objekt "List<>". Ein gefundener Wert wird in eine List Objekt hinzugefügt:

function SuperSearch {
    param (
        [System.Collections.Generic.List[Object]] $collection = @(),
        [string]$field,
        [string]$value
    )
    
    [System.Collections.Generic.List[Object]] $results = @()
    [string]$Property = $field
    [System.Collections.Generic.List[Object]] $item = @()
    foreach ($item in $collection) {
    if ( $($item.$property).ToString() -eq $value) { $results.Add($item); }
    }
    $results
}
Write-Host " -----TIME Second Powershell Nativ Function over System.Collections.Generic.List" -ForegroundColor Yellow
$timer = [System.Diagnostics.Stopwatch]::StartNew()
SuperSearch $guid "guid" $pkid | Format-Table
$timer.ElapsedMilliseconds 

Dieser Umweg spart man schon massiv an Zeit und beschleunigt um das ~3.25-Fache, so ist das Resultat in ~32 Millisekunden da. 

-----TIME Second Powershell Nativ Function over System.Collections.Generic.List

no    guid
--    ----
00989 86590a7d-5c13-4454-b40e-2bad59a5b605

32

Der grosse Umweg über C#, Warp 7

In Powershell kann man ein .NET Framework type in eine PowerShell session laden und als normales .NET Framework Objekt zugreifen. Mit einer Statischen Klasse kann man direkt die Funktion zugreifen:

$CsharpCode = @"
 
using System;
using System.Management.Automation;
using System.Collections.Generic;
 
namespace SpeedSearch
{
 
    public static class Search
    {
        public static List[Object] Find(PSObject[] collection, string column, string data, Search.Mode modus=Search.Mode.Or, string column2="", string data2="")
        {
            List[Object] results = new List[Object]();
            
            foreach(PSObject item in collection)
            {
                if(column2 == "")
                {
                    if (item.Properties[column].Value.ToString() == data) { results.Add(item); }
                }
                else
                {
                    if(modus==Search.Mode.Or)
                    if ((item.Properties[column].Value.ToString() == data ) || (item.Properties[column2].Value.ToString() == data2 )) { results.Add(item); }
                    if(modus==Search.Mode.And)
                    if ((item.Properties[column].Value.ToString() == data ) && (item.Properties[column2].Value.ToString() == data2 )) { results.Add(item); }
                    
                }
            }
            return results;
        }
        public enum Mode
        {
            And,
            Or
        }
    }
   
    
}
"@
Add-Type -ReferencedAssemblies $Assem -TypeDefinition $CsharpCode -Language CSharp 

Der Aufruf beschleunigt den Aufruf auf ~15 Millisekunden. Das mag auf den ersten Blick nicht massiv schneller zu sein. Iteriert man aber über eine oder mehrere grössere CSV so wird wie in meinem Fall die Laufzeit des Scripts von 1h auf 5min minimiert. 

Write-Host " -----TIME Second Powershell CSharp" -ForegroundColor Yellow
$timer = [System.Diagnostics.Stopwatch]::StartNew()
[SpeedSearch.Search]::Find($guid ,"guid",$pkid) | ft
$timer.ElapsedMilliseconds
-----TIME Second Powershell CSharp

no    guid
--    ----
00989 86590a7d-5c13-4454-b40e-2bad59a5b605

15

Bonus

Wenn mehrere Werte in verschiedenen Felder gesucht werden kann man den Aufruf der Funktion um ein Modus (OR und AND) erweitern und das Feld angeben, das gesucht werden soll. Dabei wird keine Signifikante Verlangsamung gemessen:

[SpeedSearch.Search]::Find($guid ,"guid",$guid,[SpeedSearch.Search+Mode]::Or,"guid",$guid) | ft
[SpeedSearch.Search]::Find($guid ,"no","00982",[SpeedSearch.Search+Mode]::And,"guid",$guid) | ft

Fazit

Beim 0815 Scripts kann man oft den einfachsten Weg gehen. Wird die Ausführungszeit aber grösser wie 10min, da lohnt sich das Script genauer anzuschauen und zu "tunen". Den je länger ein Script geht läuft man Gefahr in ein Timeout zu kommen. Man könnte sich auch überlegen eine C# Klasse zu erstellen und als Assembly zu kompilieren und da ganze in ein Powershellmodul unterzubringen.

Nichts gefunden

Es wurde zur Story Powershell Where-Object mit Warp 7 beschleunigen kein Kommentar gefunden

Information

Werbung oder Ähnliches sind nicht erlaubt, daher wird jeder Beitrag geprüft und freigegeben.
Advertising, etc. are not allowed, so any contribution is reviewed and approved.
Facebook-Webadress are not allowed, Facebook als Webadresse ist nicht erlaubt


* Die E-Mail wird nicht veröffentlicht / The email will not be published
** Bitte Zahl eintragen / Please enter the number
Ihr Kommentar
?
?
captcha Image?
?
 

Tippsammlung

Kleine Tippsammlung für mich und dijenige die sich auf meine Webseite verirrt haben.

Archiv

JahrArchiv
Tag(s):