La création de points est-elle plus lente que la simple lecture du contenu d'un fichier?

13

J'ai écrit un module PowerShell qui extrait les définitions de fonctions de différents fichiers sources (un fichier .ps1 par fonction). Cela nous permet (en équipe) de travailler sur différentes fonctions en parallèle. Le module (fichier .psm1) obtient la liste des fichiers .ps1 disponibles ...

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

... puis parcourt la liste et extrait chaque définition de fonction via une sélection de points:

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

Problème: Nous avons remarqué que la vitesse de réalisation peut varier considérablement, de 10 à 180 secondes pour environ 50 fichiers sources, en fonction de la machine sur laquelle nous testons. Nous ne pouvons pas expliquer la grande variation dans le temps pris, et pensons que nous avons contrôlé des variables telles que le type de machine, le système d'exploitation, le compte utilisateur, les autorisations d'administrateur, le profil PS, la version de PS, etc. Le temps pris peut varier sur le même hôte pour le même utilisateur du jour au lendemain.

Nous nous sommes demandés s'il s'agissait d'un problème d'accès au disque et avons testé à quelle vitesse nous pouvions simplement lire à partir d'un disque. Il s'avère que parcourir Get-Contenttous ces fichiers était très rapide, ce dont nous avons tiré parti pour résoudre le problème:

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

Pourquoi l'ajout de ces fonctions via la source de points est-il tellement plus lent que la lecture et l'exécution du contenu du fichier?

Charlie Joynt
la source

Réponses:

16

Mise en place de la science

Premièrement, quelques scripts pour nous aider à tester cela. Cela génère 2000 fichiers de script, chacun avec une seule petite fonction:

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

Cela devrait suffire à faire en sorte que les frais généraux de démarrage ne importent pas trop. Vous pouvez en ajouter plus si vous le souhaitez. Cela les charge tous en utilisant la source de points:

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

Cela les charge tous en lisant d'abord leur contenu:

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

Nous devons maintenant examiner sérieusement le fonctionnement de PowerShell. J'aime JetBrains dotPeek pour un décompilateur. Si vous avez déjà essayé d' intégrer PowerShell dans une application .NET , vous constaterez que l'assemblage qui contient la plupart des éléments pertinents est System.Management.Automation. Décompiler celui-ci dans un projet et un PDB.

Pour voir où tout ce temps mystérieux est passé, nous allons utiliser un profileur. J'aime celui intégré à Visual Studio. C'est très facile à utiliser . Ajoutez le dossier contenant le PDB aux emplacements des symboles . Maintenant, nous pouvons effectuer une exécution de profilage d’une instance PowerShell qui n’exécute que l’un des scripts de test. (Définissez les paramètres de ligne de commande à utiliser -Fileavec le chemin d'accès complet du premier script à essayer. Définissez l'emplacement de démarrage sur le dossier contenant tous les scripts minuscules.) Une fois celui-ci terminé, ouvrez les propriétés de l' powershell.exeentrée sous Cibles et modifiez-le. les arguments pour utiliser l'autre script. Cliquez ensuite avec le bouton droit sur l'élément le plus en haut dans Performance Explorer et choisissez Démarrer le profilage.. Le profileur s'exécute à nouveau à l'aide de l'autre script. Maintenant nous pouvons comparer. Assurez-vous de cliquer sur "Afficher tout le code" si l'option vous est proposée. pour moi, cela apparaît dans une zone de notifications dans la vue Résumé du rapport de profilage d'échantillonnage.

Les résultats arrivent

Sur ma machine, il Get-Contenta fallu 9 secondes à la version pour parcourir les 2 000 fichiers de script. Les fonctions importantes sur le "Hot Path" étaient:

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

Cela a beaucoup de sens: nous devons attendre Get-Contentde lire le contenu du disque, et Invoke-Expressiond’utiliser ce contenu.

Sur la version à source ponctuelle, ma machine a mis un peu plus de 15 secondes à parcourir ces fichiers. Cette fois, les fonctions du Hot Path étaient des méthodes natives:

WinVerifyTrust
CodeAuthzFullyQualifyFilename

Le second semble non documenté, mais WinVerifyTrust"effectue une action de vérification de confiance sur un objet spécifié". C'est à peu près aussi vague que possible, mais en d'autres termes, cette fonction vérifie l'authenticité d'une ressource donnée à l'aide d'un fournisseur donné. Notez que je n'ai activé aucun élément de sécurité sophistiqué pour PowerShell et que ma stratégie d'exécution de script est Unrestricted.

Ce que cela signifie

En bref, vous attendez que chaque fichier soit vérifié d’une manière ou d’une autre, probablement à la recherche d’une signature, même si cela n’est pas nécessaire si vous ne limitez pas le nombre de scripts autorisés à être exécutés. Quand vous gcet ensuite iexle contenu, c'est comme si vous aviez tapé les fonctions sur la console, il n'y a donc aucune ressource à vérifier.

Ben N
la source
2
Ben, merci pour cette superbe réponse. J'ai été impressionné par le fait que vous ayez été jusqu'à décompiler, ce qui va au-delà de tout ce que j'ai essayé. Je verrai s’il est possible de suivre votre méthode de test sur l’une des machines où ce problème est le plus aigu. Cela pourrait prendre longtemps, alors ne retenez pas votre souffle!
Charlie Joynt