Filtr różnic wydajności PowerShell a funkcja

11

Obecnie czytam książkę Windows PowerShell 3.0 krok po kroku, aby uzyskać więcej informacji na temat programu PowerShell.

Na stronie 201 autor pokazuje, że filtr jest szybszy niż funkcja z tym samym funkcjonalnie.

Ten skrypt zajmuje 2,6 sekundy na swoim komputerze:

MeasureAddOneFilter.ps1
Filter AddOne
{ 
 "add one filter"
  $_ + 1
}

Measure-Command { 1..50000 | addOne }

a ten 4,6 sekundy

MeasureAddOneFunction.ps1
Function AddOne
{  
  "Add One Function"
  While ($input.moveNext())
   {
     $input.current + 1
   }
}

Measure-Command { 1..50000 | addOne }

Jeśli uruchomię ten kod, otrzymam dokładne przeciwieństwo jego wyniku:

.\MeasureAddOneFilter.ps1
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 226
Ticks             : 2266171
TotalDays         : 2,62288310185185E-06
TotalHours        : 6,29491944444444E-05
TotalMinutes      : 0,00377695166666667
TotalSeconds      : 0,2266171
TotalMilliseconds : 226,6171

.\MeasureAddOneFunction.ps1

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 93
Ticks             : 933649
TotalDays         : 1,08061226851852E-06
TotalHours        : 2,59346944444444E-05
TotalMinutes      : 0,00155608166666667
TotalSeconds      : 0,0933649
TotalMilliseconds : 93,3649

Czy ktoś może mi to wyjaśnić?

Marcel Janus
źródło

Odpowiedzi:

13

Chyba że autor przedstawił więcej dowodów potwierdzających, być może był po prostu pełen gorącego powietrza. Przeprowadziłeś test, uzyskałeś wynik i udowodniłeś, że się myli.

Edycja: Z bloga Jeffrey Snover:

Filtr to funkcja, która ma tylko blok skryptowy procesu

Samo to nie wystarczy, aby przekonać mnie, że filtr będzie miał przewagę prędkości nad funkcją, biorąc pod uwagę, że oba mają identyczne bloki procesu.

A także, jakiego rodzaju sprzętem z lat 50. jest ten facet, w którym dodanie 4 do liczby zajmuje 4,6 sekundy?

PS C:\Users\Ryan> Measure-Command { Filter AddOne { $_ + 1 }; AddOne 1 }

TotalMilliseconds : 7.7266


PS C:\Users\Ryan> Measure-Command { Function AddOne { $_ + 1 }; AddOne 1 }    

TotalMilliseconds : 0.4108

4,6 sekundy to walnięcie. Być może autor używał jakiejś wersji Powershell w wersji CTP, zanim pliki binarne zostały ngen'owane. : P

Na koniec wypróbuj test w nowej sesji PowerShell, ale w odwrotnej kolejności. Najpierw wypróbuj funkcję, a następnie filtr - lub odwrotnie:

PS C:\Users\Ryan> Measure-Command { Function AddOne { $_ + 1 }; AddOne 1 }    

TotalMilliseconds : 6.597    


PS C:\Users\Ryan> Measure-Command { Filter AddOne { $_ + 1 }; AddOne 1 }

TotalMilliseconds : 0.4055

Widzieć? Pierwszy, który uruchomisz, będzie zawsze wolniejszy. Chodziło o wewnętrzne elementy .NET o załadowanie już rzeczy do pamięci, co przyspiesza drugą operację, niezależnie od tego, czy jest to funkcja czy filtr.

Przyznaję jednak, że Funkcja nadal wydaje się być konsekwentnie szybsza niż Filtr, niezależnie od tego, ile razy jest uruchamiana.

Measure-Command { Function AddOne($Num) { Return $Num += 1 }; 1..50000 | AddOne $_ }

TotalMilliseconds : 13.9813

Measure-Command { Filter AddOne($Num) { Return $Num += 1 }; 1..50000 | AddOne $_ }

TotalMilliseconds : 69.5301

Więc autor się mylił ... a teraz nie czuję się źle, że nigdy wcześniej nie użyłem filtra zamiast funkcji.

Ryan Ries
źródło
4

W rzeczywistości różnica jest znacznie mniejsza, jeśli użyjesz tego samego $ _ w obu testach. Nie zbadałem przyczyny, ale przypuszczam, że dzieje się tak, ponieważ autor nie stosuje tego samego podejścia w obu testach. Ponadto dane wyjściowe konsoli mogą zakłócać wyniki. Jeśli wycinasz te części, liczby są bardzo podobne. Widzieć:

Function AddOneFunction
{  
    process {
        $_ + 1
    }
}

Filter AddOneFilter
{ 
    $_ + 1
}

write-host "First"
Measure-Command { 1..50000 | AddOneFilter } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFilter } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFilter } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFilter } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFilter } | select totalMilliseconds

write-host "Second"
Measure-Command { 1..50000 | AddOneFunction } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFunction } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFunction } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFunction } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFunction } | select totalMilliseconds

Wyniki będą bardzo bliskie, nawet jeśli zmienisz kolejność poleceń.

First

TotalMilliseconds
-----------------
        84.6742
        84.7646
        89.8603
        82.3399
        83.8195
Second
        86.8978
        87.4064
        89.304
        94.4334
        87.0135

Dokumentacja mówi również, że Filtry to w zasadzie skróty do funkcji zawierających tylko blok procesu. Funkcje, o ile nie są określone za pomocą bloku procesu (lub innej techniki, takiej jak użycie zmiennych automatycznych, takich jak $ input), działają raz, nie używaj danych wejściowych i nie przechodzą do następnego polecenia w potoku.

Więcej informacji na https://technet.microsoft.com/en-us/library/hh847829.aspx i https://technet.microsoft.com/en-us/library/hh847781.aspx

Vinicius Xavier
źródło