Améliorer les performances pour l’énumération des fichiers et des dossiers à l’aide de .NET

J’ai un répertoire de base qui contient plusieurs milliers de dossiers. À l’intérieur de ces dossiers, il peut y avoir entre 1 et 20 sous-dossiers contenant entre 1 et 10 fichiers. J’aimerais supprimer tous les fichiers de plus de 60 jours. J’utilisais le code ci-dessous pour obtenir la liste des fichiers que je devrais supprimer:

DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory); FileInfo[] oldFiles = dirInfo.GetFiles("*.*", SearchOption.AllDirectories) .Where(t=>t.CreationTime < DateTime.Now.AddDays(-60)).ToArray(); 

Mais je l’ai laissé courir pendant environ 30 minutes et ce n’est toujours pas fini. Je suis curieux de savoir si quelqu’un peut voir de toute façon que je pourrais potentiellement améliorer les performances de la ligne ci-dessus ou s’il y a une autre façon de procéder que je devrais aborder de la même manière pour une meilleure performance. Suggestions?

C’est (probablement) aussi bon que ça va devenir:

 DateTime sixtyLess = DateTime.Now.AddDays(-60); DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory); FileInfo[] oldFiles = dirInfo.EnumerateFiles("*.*", SearchOption.AllDirectories) .AsParallel() .Where(fi => fi.CreationTime < sixtyLess).ToArray(); 

Changements:

  • A fait la constante des 60 jours moins DateTime , et donc moins de charge du processeur.
  • EnumerateFiles utilisés.
  • Fait la requête en parallèle.

Devrait fonctionner dans un court laps de temps (pas sûr de combien plus petit).

Voici une autre solution qui peut être plus rapide ou plus lente que la première, cela dépend des données:

 DateTime sixtyLess = DateTime.Now.AddDays(-60); DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory); FileInfo[] oldFiles = dirInfo.EnumerateDirectories() .AsParallel() .SelectMany(di => di.EnumerateFiles("*.*", SearchOption.AllDirectories) .Where(fi => fi.CreationTime < sixtyLess)) .ToArray(); 

Ici, le parallélisme est déplacé vers l'énumération du dossier principal. La plupart des modifications ci-dessus s'appliquent également.

Une alternative peut-être plus rapide consiste à utiliser WINAPI FindNextFile . Il existe un excellent outil d’énumération de répertoire plus rapide pour cela. Qui peut être utilisé comme suit:

 HashSet GetPast60(ssortingng dir) { DateTime retval = DateTime.Now.AddDays(-60); HashSet oldFiles = new HashSet(); FileData [] files = FastDirectoryEnumerator.GetFiles(dir); for (int i=0; i 

MODIFIER

Ainsi, en fonction des commentaires ci-dessous, j'ai décidé de faire ici une comparaison des solutions suggérées, ainsi que d'autres solutions auxquelles je pourrais penser. Il était assez intéressant de voir qu'EnumerateFiles semblait surperformer FindNextFile en C # , tandis AsParallel avec AsParallel était de loin le meilleur .

Cependant, la deuxième solution suggérée par newStackExchangeInstance semble produire des résultats erronés, tandis que la première semble beaucoup mieux dans ce cas (bien sûr, en supposant que l'utilisateur dispose d'un PC moderne ).

Configuration applicable:

  • Windows 7 Service Pack 1 x64
  • CPU Intel (R) Core (TM) i5-3210M à 2,50 GHz, 2,50 GHz
  • RAM: 6 Go
  • Cible de la plate-forme: x64
  • Aucune optimisation (NB: la compilation avec optimisation produira des performances considérablement médiocres)
  • Autoriser le code non sécurisé
  • Démarrer sans débogage

Voici trois captures d'écran:

Run 1

Run 2

Run 3

J'ai inclus mon code de test ci-dessous:

 static void Main(ssortingng[] args) { Console.Title = "File Enumeration Performance Comparison"; Stopwatch watch = new Stopwatch(); watch.Start(); var allfiles = GetPast60("C:\\Users\\UserName\\Documents"); watch.Stop(); Console.WriteLine("Total time to enumerate using WINAPI =" + watch.ElapsedMilliseconds + "ms."); Console.WriteLine("File Count: " + allfiles); Stopwatch watch1 = new Stopwatch(); watch1.Start(); var allfiles1 = GetPast60Enum("C:\\Users\\UserName\\Documents\\"); watch1.Stop(); Console.WriteLine("Total time to enumerate using EnumerateFiles =" + watch1.ElapsedMilliseconds + "ms."); Console.WriteLine("File Count: " + allfiles1); Stopwatch watch2 = new Stopwatch(); watch2.Start(); var allfiles2 = Get1("C:\\Users\\UserName\\Documents\\"); watch2.Stop(); Console.WriteLine("Total time to enumerate using Get1 =" + watch2.ElapsedMilliseconds + "ms."); Console.WriteLine("File Count: " + allfiles2); Stopwatch watch3 = new Stopwatch(); watch3.Start(); var allfiles3 = Get2("C:\\Users\\UserName\\Documents\\"); watch3.Stop(); Console.WriteLine("Total time to enumerate using Get2 =" + watch3.ElapsedMilliseconds + "ms."); Console.WriteLine("File Count: " + allfiles3); Stopwatch watch4 = new Stopwatch(); watch4.Start(); var allfiles4 = RunCommand(@"dir /a: /b /s C:\Users\UserName\Documents"); watch4.Stop(); Console.WriteLine("Total time to enumerate using Command Prompt =" + watch4.ElapsedMilliseconds + "ms."); Console.WriteLine("File Count: " + allfiles4); Console.WriteLine("Press Any Key to Continue..."); Console.ReadLine(); } private static int RunCommand(ssortingng command) { var process = new Process() { StartInfo = new ProcessStartInfo("cmd") { UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, CreateNoWindow = true, Arguments = Ssortingng.Format("/c \"{0}\"", command), } }; int count = 0; process.OutputDataReceived += delegate { count++; }; process.Start(); process.BeginOutputReadLine(); process.WaitForExit(); return count; } static int GetPast60Enum(ssortingng dir) { return new DirectoryInfo(dir).EnumerateFiles("*.*", SearchOption.AllDirectories).Count(); } private static int Get2(ssortingng myBaseDirectory) { DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory); return dirInfo.EnumerateFiles("*.*", SearchOption.AllDirectories) .AsParallel().Count(); } private static int Get1(ssortingng myBaseDirectory) { DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory); return dirInfo.EnumerateDirectories() .AsParallel() .SelectMany(di => di.EnumerateFiles("*.*", SearchOption.AllDirectories)) .Count(); } private static int GetPast60(ssortingng dir) { return FastDirectoryEnumerator.GetFiles(dir, "*.*", SearchOption.AllDirectories).Length; } 

NB: Je me suis concentré sur le décompte dans la date de référence non modifiée.

Je me rends compte que le parti arrive très tard, mais si quelqu’un d’autre le recherche, vous pouvez accélérer les choses de façon considérable en analysant directement le MFT ou le FAT du système de fichiers. Cela nécessite des privilèges d’administrateur, car je pense que le système reviendra. tous les fichiers, quelle que soit la sécurité, mais peuvent prendre probablement au moins 30 minutes entre 30 minutes et 30 minutes pour l’étape de l’énumération.

Une bibliothèque pour NTFS est ici https://github.com/LordMike/NtfsLib. Il existe également https://discutils.codeplex.com/ que je n’ai pas personnellement utilisé.

J’utiliserais uniquement ces méthodes pour la découverte initiale de fichiers de plus de x jours, puis je les vérifierais individuellement avant de les supprimer, cela risque d’être excessif, mais je suis prudent comme ça.

Vous utilisez un Linq. Ce serait plus rapide si vous écriviez votre propre méthode de recherche récursive de répertoires avec votre cas particulier.

 public static DateTime retval = DateTime.Now.AddDays(-60); public static void WalkDirectoryTree(System.IO.DirectoryInfo root) { System.IO.FileInfo[] files = null; System.IO.DirectoryInfo[] subDirs = null; // First, process all the files directly under this folder try { files = root.GetFiles("*.*"); } // This is thrown if even one of the files requires permissions greater // than the application provides. catch (UnauthorizedAccessException e) { // This code just writes out the message and continues to recurse. // You may decide to do something different here. For example, you // can try to elevate your privileges and access the file again. log.Add(e.Message); } catch (System.IO.DirectoryNotFoundException e) { Console.WriteLine(e.Message); } if (files != null) { foreach (System.IO.FileInfo fi in files) { if (fi.LastWriteTime < retval) { oldFiles.Add(files[i]); } Console.WriteLine(fi.FullName); } // Now find all the subdirectories under this directory. subDirs = root.GetDirectories(); foreach (System.IO.DirectoryInfo dirInfo in subDirs) { // Resursive call for each subdirectory. WalkDirectoryTree(dirInfo); } } } 

Si vous voulez vraiment améliorer les performances, mettez la main à la NtQueryDirectoryFile et utilisez le NtQueryDirectoryFile interne à Windows, avec une grande taille de mémoire tampon.

FindFirstFile est déjà lent et, si FindFirstFileEx est un peu meilleur, les meilleures performances seront FindFirstFileEx appelant directement la fonction native.

La méthode Get1 dans la réponse ci-dessus (#itsnotalie & #Chibueze Opata) est manquante pour compter les fichiers du répertoire racine. Elle devrait donc se lire:

 private static int Get1(ssortingng myBaseDirectory) { DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory); return dirInfo.EnumerateDirectories() .AsParallel() .SelectMany(di => di.EnumerateFiles("*.*", SearchOption.AllDirectories)) .Count() + dirInfo.EnumerateFiles("*.*", SearchOption.TopDirectoryOnly).Count(); }