Skip Ribbon Commands
Skip to main content

Ondrej Sevecek's Blog

:

Engineering and troubleshooting by Directory Master!
Ondrej Sevecek's Blog > Posts > Porovnání výkonu PowerShell pipe a programátorského přístupu za pomoci foreach a if
září 19
Porovnání výkonu PowerShell pipe a programátorského přístupu za pomoci foreach a if

Dneska mě napadlo trošku porovnat rychlosti dvou programátorských přístupů v jazyce PowerShell. PowerShell umožňuje ve skriptech používat pajpu (pipe) a její příkazy jako je ForEach-Object, nebo Where-Object, což je někdy úžasně jednoduché. Nebo na to můžete jít spíše programátorskou syntaxí a udělat to stejné pomocí foreach a if klíčových slov.

Je v tom nějaký rozdíl? Tak rozdíly jsou například v nemožnosti použít klíčové slovo break uvnitř pajpy (teda můžete, ale dělá to věci :-)). Nebo Where-Object je málo flexibilní a těžko se dá udělat složitější else, nebo elseif logika uvnitř tohoto cmdletu. Podobně $MyInvocation.MyCommand je prázdný, pokud se použije uvnitř pajpy (pipe).

Ale o to mi tu nešlo. Chtěl jsem porovnat rychlosti. Tak jsem si napsal následující skriptík. V první části se jenom generuje pole hodnot. To trvá hodně dlouho a obě metody jsou poměrně srovnatelné. To je tím, že většina času, se spotřebuje na plnění toho pole. Použil jsem ArrayList, protože kdybych to přidával do pole, tak by to bylo dokonce nelineárně strašlivě zdlouhavé - pole se kopíruje při přidávání prvku. Ale i zde je vidět, že cmdlet ForEach-Object je pomalejší.

Podstatně drsnější porovnání ovšem nastane v druhé části, kde se to pole už jenom projíždí. Tam je kombinace foreach a if dokonce cca 10x rychlejší, než cmdlet ForEach-Object a Where-Object.

$bigArrayCount = 200000
[System.Collections.ArrayList] $bigArray = @()
$idArray = (1..$bigArrayCount)

$bigArray = @()
$start = Get-Date
$idArray | % { $bigArray.Add($_) | Out-Null }
$end = Get-Date

Write-Host ('Filling a big array with ForEach-Object: {0:N1} sec.' -f ($end - $start).TotalSeconds) -For Green

$bigArray = @()
$start = Get-Date
foreach ($id in $idArray) { $bigArray.Add($_) | Out-Null }
$end = Get-Date

Write-Host ('Filling a big array with foreach keyword: {0:N1} sec.' -f ($end - $start).TotalSeconds) -For Green


$start = Get-Date
$bigArray | ? { $_ -gt ($bigArrayCount / 4) } | % { $oneCalc = 3 * $_ - 80 }
$end = Get-Date

Write-Host ('Going through the big array with Where-Object and ForEach-Object: {0:N1} sec.' -f ($end - $start).TotalSeconds) -For Green

$start = Get-Date
foreach ($one in $bigArray) { if ($one -gt ($bigArrayCount / 4)) { $oneCalc = 3 * $_ - 80 } } 
$end = Get-Date

Write-Host ('Going through the big array with foreach and if keywords: {0:N1} sec.' -f ($end - $start).TotalSeconds) -For Green

Závěr

Pajpování je paráda, ale pro větší kódy se to moc nehodí. Nehledě na to, že uvnitř nejde použít break, což je pro mě osobně dost podstatné.

Poznámka

Mimochodem, výsledné hodnoty na mém noťasu HP 6730b s procesorem Intel Core 2 Duo CPU @2.26 GHz s pamětí o rychlosti 800 jsou:

Filling a big array with ForEach-Object: 22,3 sec.
Filling a big array with foreach keyword: 14,3 sec.
Going through the big array with Where-Object and ForEach-Object: 9,7 sec.
Going through the big array with foreach and if keywords: 0,6 sec.

Tak kdyby to někdo třeba vyzkoušel, speciálně by mě osobně zajímal nějaký Lenovo T430, tak to bych byl rád :-) Pokud chcete vědět, jak rychlou máte RAM a CPUčka, zjistíte to takto:

gwmi win32_physicalmemory | ft Capacity, Speed
gwmi win32_processor | ft Name, NumberOfCores, NumberOfLogicalProcessors

Comments

rychlost

Ono je to podle mého laického názoru poměrně logické - pajpa je taková "zkratka", ukazatel na nějakou část paměti, kdežto where-object je programově implementované zpracování nějakých dat. Nebo ne?
RaT on 19.9.2013 9:38

Re: Porovnání výkonu PowerShell pipe a programátorského přístupu za pomoci foreach a if

tak nějak. foreach a if jsou elementy jazyka, takže to ten procesor umí prostě provést "na místě", zatímco ForEach-Object a Where-Object jsou cmdlety, to znamená že to jsou normální .NET DLL knihovny (nebo součást nějaké DLL knihovny), ve které je k tomu vždycky třída a má nějaké funkce. Takže se tomu musí předat parametry, pak je tam režie na údržbu té fronty apod.

Ale řekl bych, že desetinásobek je hrůzně veliká režie, to jsem tak moc nečekal.
ondass on 19.9.2013 9:47

T420 a T430

tak tu mas vysledky z T420 a T430

T420 - Intel(R) Core(TM) i5-2410M CPU @ 2.30GHz, 1333
Filling a big array with ForEach-Object: 31,9 sec.
Filling a big array with foreach keyword: 21,2 sec.
Going through the big array with Where-Object and ForEach-Object: 10,7 sec.
Going through the big array with foreach and if keywords: 0,4 sec.

T430 - Intel(R) Core(TM) i5-3320M CPU @ 2.60GHz, 1600
Filling a big array with ForEach-Object: 13,1 sec.
Filling a big array with foreach keyword: 7,8 sec.
Going through the big array with Where-Object and ForEach-Object: 5,0 sec.
Going through the big array with foreach and if keywords: 0,4 sec.

jn on 19.9.2013 10:09

Re: Porovnání výkonu PowerShell pipe a programátorského přístupu za pomoci foreach a if

paráda! díky!
ondass on 19.9.2013 10:27

Why use foreach vs foreach-object.

VasekB on 19.9.2013 11:57

Dell M4600

Intel(R) Core(TM) i7-2620M CPU @ 2.70GHz ; RAM: 4GB , 1333

Filling a big array with ForEach-Object: 25,0 sec.
Filling a big array with foreach keyword: 16,5 sec.
Going through the big array with Where-Object and ForEach-Object: 8,6 sec.
Going through the big array with foreach and if keywords: 0,4 sec.
VasekB on 19.9.2013 12:07

Re: Porovnání výkonu PowerShell pipe a programátorského přístupu za pomoci foreach a if

taky děkuju! je zajímavé, jak se to liší na různých místech :-)
ondass on 19.9.2013 12:41

[Void] a explicitni deklarace...

$bigArrayCount = 200000
[System.Collections.ArrayList] $bigArray = @()
$idArray = (1..$bigArrayCount)

$bigArray = @()
$start = Get-Date
foreach ($id in $idArray) { $bigArray.Add($_) | Out-Null }
$end = Get-Date

Write-Host ('Filling a big array with foreach keyword: {0:N1} sec.' -f ($end - $start).TotalSeconds) -For Green

$bigArray = @()
$start = Get-Date
$idArray | % { $bigArray.Add($_) | Out-Null }
$end = Get-Date

Write-Host ('Filling a big array with ForEach-Object: {0:N1} sec.' -f ($end - $start).TotalSeconds) -For Green

$bigArray = @()
$start = Get-Date
$idArray | % { [Void]$bigArray.Add($_) }
$end = Get-Date

Write-Host ('Filling a big array with ForEach-Object using Void: {0:N1} sec.' -f ($end - $start).TotalSeconds) -For Green

$bigArray = @()
$start = Get-Date
foreach ($id in $idArray) { [Void]$bigArray.Add($_) }
$end = Get-Date

Write-Host ('Filling a big array with foreach keyword using Void: {0:N1} sec.' -f ($end - $start).TotalSeconds) -For Green

$bigArray = @()
[int[]]$idArray = (1..$bigArrayCount)
[int]$id = 0
$start = Get-Date
foreach ($id in $idArray) { [Void]$bigArray.Add($_) }
$end = Get-Date

Write-Host ('Filling a big array with foreach keyword using Void and explicit declaration: {0:N1} sec.' -f ($end - $start).TotalSeconds) -For Green
zl on 19.9.2013 13:49

Re: Porovnání výkonu PowerShell pipe a programátorského přístupu za pomoci foreach a if

pěkně! opět cca 1/3 bez pajpy úspora. jenže to se nedá porovnávat s těmi cykly, protože podobná operace se v cyklu normálně nedělá, nebo pokud se dělá, tak je to jen zlomek objemu kódu uvnitř.

ale jo, je pěkně vidět, jak je pajpa předražená :-)
ondass on 19.9.2013 14:07

Pipe zrychluje vstup a výstup prodového zpracování

Pipe je rychlejší, pokud čtu a zapisuji do souboru.

Mám log soubor cca 20 MB. Z něj mě zajímá jen část informací, co se zazálohovalo. Následující script :



Measure-Command{
Get-Content .\soubor.txt | select -Skip 4 | Select-String "^.{178}[^\\]+\\" | %{  $_ -replace "^.{178}[^\\]+\\([^ ]+) .*$", "`${1}" } | Out-File -FilePath vypis.txt
} | %{ Write-Host ('Use pipe: {0:N5} sec.' -f $_.TotalSeconds) }



Measure-Command{
    $content = Get-Content .\soubor.txt
    $i = 0
    Remove-Item vypis2.txt
    foreach($line in $content){
        if($i++ -gt 4 -and $line -match  "^.{178}[^\\]+\\"){
            $out_line = ($line -replace "^.{178}[^\\]+\\([^ ]+) .*$", "`${1}" )
            $out_line >> vypis2.txt
        }
    }
}| %{ Write-Host ('Use foreach: {0:N5} sec.' -f $_.TotalSeconds) }



A výstup:
Use pipe: 4.31696 sec.
Use foreach: 18.94665 sec.


Při použití pipe se zpracovává vždy jen jeden řádek, jak je dostávám z Get-Content a ten pak zapisuji do souboru. Čtení i zápis můžou pozastavit pipe podle potřeby. Takže to funguje transparentně jako na Unixu.
ZbyněkD on 24.10.2013 14:57

Re: Porovnání výkonu PowerShell pipe a programátorského přístupu za pomoci foreach a if

Zdravím Ondřeji, souhlasím s tím, že Foreach-Object  (a používání pipy obecně) je často pomalejší než používání klíčových slov. Nicméně je zde ještě několik věcí, které mají na vaše měření zásadní vliv

1) Váš skript dělá něco jiného než má. Používáte foreach keyword nesprávně, v těle příkazu používáte $_ namísto vámi definovaného $id. (stejně tak s $one)

2) Při plnění pole měříte něco jiného než jste zamýšlel. Zřejmě jste náhodou vybral nejméně efektivní způsob potlačení výstupu, a tak neměříte efektivitu foreach vs. Foreach-Object, ale neefektivitu výstupu do Out-Null. Pokud byste pro měření použil Measure-Command, který potlačí výstup za vás ( a navíc je přesnější ) získal byste úplně jiné výsledky.
Níže přikládám skript a výstup na mém PC, z kterého lze vyčíst, že foreach plní pole přibližně dvacetkrát rychleji, tedy podobný poměr jako s opraveným procházením pole.

$bigArrayCount = 200000
[System.Collections.ArrayList] $bigArray = @()
$idArray = (1..$bigArrayCount)

$bigArray.Clear()
(Measure-Command {
$idArray | ForEach-Object { $bigArray.Add($_)  }
}).TotalSeconds | Foreach-Object { 'Not descarding output explicitly  and Foreach-Object: {0} s ' -f $_ }
$bigArray.Clear()
(Measure-Command {
$idArray | ForEach-Object { $null = $bigArray.Add($_)  }
}).TotalSeconds | Foreach-Object { 'Using $null = and Foreach-Object: {0} s ' -f $_ }
$bigArray.Clear()
(Measure-Command {
$idArray | ForEach-Object { $bigArray.Add($_) > $null  }
}).TotalSeconds | Foreach-Object { 'Using > $null and Foreach-Object: {0} s ' -f $_ }
$bigArray.Clear()
(Measure-Command {
$idArray | ForEach-Object { [void]$bigArray.Add($_)  }
}).TotalSeconds | Foreach-Object { 'Using [void] and Foreach-Object: {0} s ' -f $_ }
$bigArray.Clear()
(Measure-Command {
foreach ($id in $idArray) { $null = $bigArray.Add($id) }
}).TotalSeconds | Foreach-Object { 'Using $null = and foreach keyword: {0} s ' -f $_ }
(Measure-Command {
foreach ($id in $idArray) { $bigArray.Add($id) }
}).TotalSeconds | Foreach-Object { 'Not descarding output explicitly  and foreach keyword: {0} s ' -f $_ }
""
"Wrong measuring:"
$bigArray.Clear()
(Measure-Command {
$idArray | ForEach-Object { $bigArray.Add($_) | Out-Null }
}).TotalSeconds | Foreach-Object { 'Using Out-Null and Foreach-Object: {0} s ' -f $_ }
$bigArray.Clear()
(Measure-Command {
foreach ($id in $idArray) { $bigArray.Add($id) | Out-Null }
}).TotalSeconds | Foreach-Object { 'Using Out-Null and foreach keyword: {0} s ' -f $_ }

Not descarding output explicitly  and Foreach-Object: 8,3999473 s
Using $null = and Foreach-Object: 8,3799751 s
Using > $null and Foreach-Object: 8,6363801 s
Using [void] and Foreach-Object: 8,405792 s
Using $null = and foreach keyword: 0,4713866 s
Not descarding output explicitly  and foreach keyword: 0,4858372 s

Wrong measuring:
Using Out-Null and Foreach-Object: 23,8503122 s
Using Out-Null and foreach keyword: 15,9501333 s



 


nohandle on 14.1.2014 13:31

Re: Porovnání výkonu PowerShell pipe a programátorského přístupu za pomoci foreach a if

díky za opravu $_

to s tím Out-Null už tu ale někdo zmiňoval, na základě toho jsem si už tehdy opravil ve svých skriptech cca 50 použití na [void] :-) Nechal jsem ale ten skript v původním znění, aby ty komentáře štymovaly :-)

díky!
ondass on 14.1.2014 13:51

Add Comment

Title


Pole Title nemusíte vyplňovat, doplní se to samo na stejnou hodnotu jako je nadpis článku.

Author *


Pole Author nesmí být stejné jako pole Title! Mám to tu jako ochranu proti spamu. Roboti to nevyplní dobře :-)

Body *


Type number two as digit *


Semhle vyplňte číslici dvě. Předchozí antispemové pole nefunguje úplně dokonale, zdá se, že jsou i spamery, které pochopily, že je občas potřeba vyplnit autora :-)

Email


Emailová adresa, pokud na ni chcete ode mě dostat odpověď. Nikdo jiný než já vaši emailovou adresu neuvidí.

Attachments