使用.NET提高枚举文件和文件夹的性能

我有一个包含几千个文件夹的基目录。 在这些文件夹中,可以有1到20个子文件夹,包含1到10个文件。 我想删除所有超过60天的文件。 我使用下面的代码来获取我必须删除的文件列表:

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

但我让它运行了大约30分钟,但仍然没有完成。 我很好奇是否有人能够看到我可以提高上述线路的性能,或者如果有不同的方式我应该完全接近这个以获得更好的性能? 建议?

这(可能)和它一样好:

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

变化:

  • 使DateTime减少了60天,因此减少了CPU负载。
  • 使用EnumerateFiles
  • 使查询平行。

应该在较短的时间内运行(不确定多少)。

这是另一个可能比第一个更快或更慢的解决方案,它取决于数据:

 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(); 

在这里,它将并行性移动到主文件夹枚举。 上述大部分变化也适用。

可能更快的替代方法是使用WINAPI FindNextFile 。 有一个非常好的快速目录枚举工具 。 可以使用如下:

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

编辑

因此,基于以下评论,我决定在此处以及我能想到的其他建议解决方案的基准。 有趣的是, EnumerateFiles似乎在C#中表现出了FindNextFile ,而使用AsParallel EnumerateFiles是迄今为止最好的

但是, newStackExchangeInstance建议的第二个解决方案似乎产生了错误的结果,而在这种情况下第一个似乎好得多(当然假设用户有一台现代PC )。

适用配置:

  • Windows 7 Service Pack 1 x64
  • Intel(R)Core(TM)i5-3210M CPU @ 2.50GHz 2.50GHz
  • 内存:6GB
  • 平台目标:x64
  • 没有优化(注意:使用优化进行编译会产生极差的性能)
  • 允许UnSafe代码
  • 无需调试即可启动

以下是三个截图:

运行1

跑2

跑3

我在下面列出了我的测试代码:

 static void Main(string[] 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(string command) { var process = new Process() { StartInfo = new ProcessStartInfo("cmd") { UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, CreateNoWindow = true, Arguments = String.Format("/c \"{0}\"", command), } }; int count = 0; process.OutputDataReceived += delegate { count++; }; process.Start(); process.BeginOutputReadLine(); process.WaitForExit(); return count; } static int GetPast60Enum(string dir) { return new DirectoryInfo(dir).EnumerateFiles("*.*", SearchOption.AllDirectories).Count(); } private static int Get2(string myBaseDirectory) { DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory); return dirInfo.EnumerateFiles("*.*", SearchOption.AllDirectories) .AsParallel().Count(); } private static int Get1(string myBaseDirectory) { DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory); return dirInfo.EnumerateDirectories() .AsParallel() .SelectMany(di => di.EnumerateFiles("*.*", SearchOption.AllDirectories)) .Count(); } private static int GetPast60(string dir) { return FastDirectoryEnumerator.GetFiles(dir, "*.*", SearchOption.AllDirectories).Length; } 

注意:我专注于基准未修改日期的计数。

我意识到派对已经很晚了,但是如果其他人正在寻找这个,那么你可以通过直接解析文件系统的MFT或FAT来提高数量级,这需要管理员权限,因为我认为它会返回所有文件无论安全性如何,但至少在枚举阶段可能需要30分钟到30秒。

NTFS的库在这里https://github.com/LordMike/NtfsLib还有https://discutils.codeplex.com/我没有亲自使用过。

我只会使用这些方法来初始发现超过x天的文件,然后在删除之前validation它们,它可能有点矫枉过正,但我​​很谨慎。

你正在使用Linq。 如果您使用特殊情况编写自己的方法来递归搜索目录,那会更快。

 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); } } } 

如果你真的想提高性能, NtQueryDirectoryFile搞定一下,使用Windows内部的NtQueryDirectoryFile ,缓冲区大小。

FindFirstFile已经很慢了,虽然FindFirstFileEx稍微好一点,但最佳性能将来自直接调用本机函数。

上面的答案(#itsnotalie和#Chibueze Opata)中的Get1方法缺少计算根目录中的文件,所以它应该是:

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