Czy pobieranie kropek jest wolniejsze niż tylko czytanie zawartości pliku?

13

Napisałem moduł PowerShell, który pobiera definicje funkcji z różnych plików źródłowych (tj. Jeden plik .ps1 na funkcję). Dzięki temu możemy (jako zespół) pracować nad różnymi funkcjami równolegle. Moduł (plik .psm1) pobiera listę dostępnych plików .ps1 ...

$Functions = Get-ChildItem -Path $FunctionPath *.ps1

... następnie przegląda listę i pobiera definicje każdej funkcji za pomocą kropkowania:

foreach($Function in $Functions) {
  . $Function.Fullname                                     # Can be slow
}

Problem: Zauważyliśmy, że szybkość, z jaką wykonuje się to, może się bardzo różnić, od 10 do 180 sekund dla około 50 plików źródłowych, w zależności od tego, na jakiej maszynie testujemy. Nie jesteśmy w stanie wyjaśnić dużej zmienności czasu i uważamy, że kontrolowaliśmy zmienne, takie jak typ komputera, system operacyjny, konto użytkownika, uprawnienia administratora, profil PS, wersja PS itp. Czas może się różnić na tym samym hoście dla tego samego hosta użytkownik z dnia na dzień.

Zastanawialiśmy się, czy to był problem z dostępem do dysku i sprawdziliśmy, jak szybko możemy po prostu odczytać z dysku. Okazuje się, że bieganie Get-Contentpo wszystkich tych plikach było bardzo szybkie, co wykorzystaliśmy w obejściu problemu:

foreach($Function in $Functions) {
  Invoke-Expression (Get-Content $Function.Fullname -Raw)  # Is quick
}

Dlaczego dodawanie tych funkcji za pomocą pobierania kropek jest o wiele wolniejsze niż czytanie i wykonywanie zawartości pliku?

Charlie Joynt
źródło

Odpowiedzi:

17

Tworzenie nauki

Po pierwsze, kilka skryptów, które pomogą nam to przetestować. Generuje to 2000 plików skryptów, każdy z jedną małą funkcją:

1..2000 | % { "Function Test$_(`$someArg) { Return `$someArg * $_ }" > "test$_.ps1" }

To powinno wystarczyć, aby normalny koszt początkowy nie miał zbytniego znaczenia. Możesz dodać więcej, jeśli chcesz. Spowoduje to załadowanie ich wszystkich za pomocą pozyskiwania kropek:

dir test*.ps1 | % {. $_.FullName}

Spowoduje to załadowanie ich wszystkich, najpierw czytając ich zawartość:

dir test*.ps1 | % {iex (gc $_.FullName -Raw)}

Teraz musimy dokonać poważnej inspekcji działania programu PowerShell. Lubię JetBrains dotPeek dla dekompilatora. Jeśli kiedykolwiek próbowałeś osadzić PowerShell w aplikacji .NET , przekonasz się, że jest to zestaw zawierający większość istotnych elementów System.Management.Automation. Zdekompiluj to do projektu i PDB.

Aby zobaczyć, gdzie spędza się cały ten tajemniczy czas, użyjemy profilera. Podoba mi się ten wbudowany w Visual Studio. Jest bardzo łatwy w użyciu . Dodaj folder zawierający PDB do lokalizacji symboli . Teraz możemy przeprowadzić profilowanie instancji PowerShell, która po prostu uruchamia jeden ze skryptów testowych. (Ustaw parametry wiersza polecenia do użycia -Filez pełną ścieżką pierwszego skryptu do wypróbowania. Ustaw lokalizację uruchamiania do folderu zawierającego wszystkie małe skrypty.) Po zakończeniu, otwórz Właściwości w powershell.exepozycji pod Celami i zmień argumenty, aby użyć drugiego skryptu. Następnie kliknij prawym przyciskiem myszy najwyższy element w Eksploratorze wydajności i wybierz opcję Rozpocznij profilowanie. Program profilujący uruchamia się ponownie przy użyciu drugiego skryptu. Teraz możemy porównać. Upewnij się, że kliknąłeś „Pokaż cały kod”, jeśli podano opcję; dla mnie to pokazuje się w obszarze powiadomień w widoku Podsumowanie przykładowego raportu profilowania.

Wyniki wchodzą

Na moim komputerze Get-Contentwersja zajęła 9 sekund przejście przez pliki skryptów 2000. Ważnymi funkcjami „Hot Path” były:

Microsoft.PowerShell.Commands.GetContentCommand.ProcessRecord
Microsoft.PowerShell.Commands.InvokeExpressionCommand.ProcessRecord

Ma to sens: musimy poczekać na Get-Contentodczyt zawartości z dysku i musimy poczekać, aż Invoke-Expressionz niej skorzystamy.

W wersji źródłowej moja maszyna poświęciła nieco ponad 15 sekund na przeglądanie tych plików. Tym razem funkcje na Hot Path były metodami natywnymi:

WinVerifyTrust
CodeAuthzFullyQualifyFilename

Drugi wydaje się być nieudokumentowany, ale WinVerifyTrust„wykonuje akcję weryfikacji zaufania do określonego obiektu”. To jest tak niejasne, jak to tylko możliwe, ale innymi słowy, funkcja ta weryfikuje autentyczność danego zasobu za pomocą danego dostawcy. Zauważ, że nie włączyłem żadnych wymyślnych zabezpieczeń dla PowerShell, a moje zasady wykonywania skryptów są Unrestricted.

Co to znaczy

Krótko mówiąc, czekasz na jakąś weryfikację każdego pliku, prawdopodobnie sprawdzoną pod kątem podpisu, nawet jeśli nie jest to konieczne, gdy nie ograniczysz skryptów, które mogą być uruchamiane. Kiedy ty, gca potem iexzawartość, to tak, jakbyś wpisał funkcje w konsoli, więc nie ma zasobów do zweryfikowania.

Ben N.
źródło
2
Ben, dzięki za tę wspaniałą odpowiedź. Jestem pod wrażeniem, że posunąłeś się nawet do dekompilacji, co stanowi krok ponad wszystko, czego próbowałem. Zobaczę, czy jest jakiś sposób, aby śledzić twoją metodę testowania na jednej z maszyn, na których ten problem jest najbardziej dotkliwy. Może to potrwać długo, więc nie wstrzymuj oddechu!
Charlie Joynt,