Le but du script est le suivant:
Jusqu'ici (3) est la partie difficile.
Voici ce que j'ai écrit et testé jusqu'à présent. Cela fonctionne parfaitement sur des dossiers contenant une centaine, voire mille fichiers:
$hostname=hostname
$directory = "foo"
$dteCurrentDate = Get-Date –f "yyyy/MM/dd"
$FolderItems = Get-ChildItem $directory -recurse
$Measurement = $FolderItems | Measure-Object -property length -sum
$colitems = $FolderItems | measure-Object -property length -sum
"$hostname;{0:N2}" -f ($colitems.sum / 1MB) + "MB;" + $Measurement.count + " files;" + "$dteCurrentDate"
Sur les dossiers contenant des millions de fichiers, cependant, la variable $colitems
devient tellement massive à partir de la collecte d'informations de millions de fichiers qu'elle rend le système instable. Existe-t-il un moyen plus efficace de dessiner et de stocker cette information?
Si vous utilisez la diffusion en continu et le traitement en pipeline, vous devriez réduire beaucoup le problème avec (3), car lorsque vous diffusez en continu, chaque objet est transmis le long du pipeline au fur et à mesure de leur disponibilité et ne nécessite pas beaucoup de mémoire. traiter des millions de fichiers (bien que cela prenne du temps).
Get-ChildItem $directory -recurse | Measure-Object -property length -sum
Je ne crois pas que la déclaration de @ Stej, Get-ChildItem probably reads all entries in the directory and then begins pushing them to the pipeline.
, soit vraie. Le traitement en pipeline est un concept fondamental de PowerShell (fournissez-le avec les applets de commande, scripts, etc.). Cela garantit à la fois que les objets traités passent dans le pipeline un par un au fur et à mesure de leur disponibilité et que uniquement lorsqu'ils sont nécessaires. Get-ChildItem
ne va pas se comporter différemment.
Vous en trouverez un bon exemple dans Présentation du pipeline Windows PowerShell.
Citant de cela:
La commande Out-Host -Paging est un élément de pipeline utile chaque fois que vous avoir une longue sortie que vous voudriez afficher lentement. Il est particulièrement utile si l'opération nécessite beaucoup de ressources processeur. Parce que le traitement est transféré à la cmdlet Out-Host quand elle a un page complète prête à être affichée, les applets de commande qui la précèdent dans le opération d’arrêt du pipeline jusqu’à ce que la page de sortie suivante soit disponible . Vous pouvez le voir si vous utilisez le gestionnaire de tâches Windows pour surveiller le processeur et utilisation de la mémoire par Windows PowerShell.
Exécutez la commande suivante:
Get-ChildItem C:\Windows -Recurse
. Comparez l'utilisation du processeur et de la mémoire à cette commande:Get-ChildItem C:\Windows -Recurse | Out-Host -Paging
.
Benchmark sur l'utilisation de Get-ChildItem
sur c:\
(environ 179516 fichiers, pas des millions, mais suffisamment bon):
L'utilisation de la mémoire après l'exécution de $a = gci c:\ -recurse
(puis de $a.count
) était de 527,332K
.
L'utilisation de la mémoire après l'exécution de gci c:\ -recurse | measure-object
était 59,452K
et n'a jamais dépassé 80,000K
.
(Mémoire - Ensemble de travail privé - à partir de TaskManager, voir la mémoire pour le processus powershell.exe
. Au départ, il s'agissait de 22,000K
.)
J'ai aussi essayé avec deux millions de fichiers (cela m'a pris du temps pour les créer!)
Expérience similaire:
L'utilisation de la mémoire après l'exécution de $a = gci c:\ -recurse
(puis de $a.count
) était de 2,808,508K
.
L'utilisation de la mémoire lors de l'exécution de gci c:\ -recurse | measure-object
était 308,060K
et n'a jamais dépassé 400,000K
. Une fois terminé, il a dû effectuer une [GC]::Collect()
pour pouvoir revenir aux niveaux 22,000K
.
Je suis toujours convaincu que Get-ChildItem
et le traitement en pipeline peuvent vous apporter d’importantes améliorations de la mémoire, même pour des millions de fichiers.
Get-ChildItem
lit probablement toutes les entrées du répertoire, puis commence à les envoyer au pipeline. Si Get-ChildItem
ne fonctionne pas bien, essayez de passer à .NET 4.0 et utilisez EnumerateFiles
et EnumeratedDirectories
:
function Get-HugeDirStats($directory) {
function go($dir, $stats)
{
foreach ($f in [system.io.Directory]::EnumerateFiles($dir))
{
$stats.Count++
$stats.Size += (New-Object io.FileInfo $f).Length
}
foreach ($d in [system.io.directory]::EnumerateDirectories($dir))
{
go $d $stats
}
}
$statistics = New-Object PsObject -Property @{Count = 0; Size = [long]0 }
go $directory $statistics
$statistics
}
#example
$stats = Get-HugeDirStats c:\windows
Ici, la partie la plus chère est celle avec New-Object io.FileInfo $f
, car EnumerateFiles
renvoie uniquement les noms de fichiers. Donc, si seul le nombre de fichiers est suffisant, vous pouvez commenter la ligne.
Voir la question relative au débordement de pile Comment exécuter PowerShell avec le runtime .NET 4? Pour apprendre à utiliser .NET 4.0.
Vous pouvez également utiliser de vieilles méthodes simples et rapides, mais lire tous les fichiers du répertoire. Cela dépend donc de vos besoins, essayez-le. Plus tard, il y a une comparaison de toutes les méthodes.
function Get-HugeDirStats2($directory) {
function go($dir, $stats)
{
foreach ($f in $dir.GetFiles())
{
$stats.Count++
$stats.Size += $f.Length
}
foreach ($d in $dir.GetDirectories())
{
go $d $stats
}
}
$statistics = New-Object PsObject -Property @{Count = 0; Size = [long]0 }
go (new-object IO.DirectoryInfo $directory) $statistics
$statistics
}
Comparaison:
Measure-Command { $stats = Get-HugeDirStats c:\windows }
Measure-Command { $stats = Get-HugeDirStats2 c:\windows }
Measure-Command { Get-ChildItem c:\windows -recurse | Measure-Object -property length -sum }
TotalSeconds : 64,2217378
...
TotalSeconds : 12,5851008
...
TotalSeconds : 20,4329362
...
@manojlds: Le pipeline est un concept fondamental. Mais en tant que concept, cela n’a rien à voir avec les fournisseurs. Le fournisseur de système de fichiers s'appuie sur l'implémentation .NET (.NET 2.0) qui ne dispose pas de capacités d'évaluation paresseuses (~ énumérateurs). Vérifiez cela vous-même.
La fonction suivante est assez cool et permet de calculer rapidement la taille d'un dossier, mais cela ne fonctionne pas toujours (surtout en cas de problème d'autorisation ou de chemin d'accès trop long au dossier).
Function sizeFolder($path) # Return the size in MB.
{
$objFSO = New-Object -com Scripting.FileSystemObject
("{0:N2}" -f (($objFSO.GetFolder($path).Size) / 1MB))
}