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

Abe*_*ler 17 .net c# performance

我有一个包含几千个文件夹的基目录.在这些文件夹中,可以有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();
Run Code Online (Sandbox Code Playgroud)

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

It'*_*ie. 21

这(可能)和它一样好:

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

变化:

  • 使得60天不那么DateTime恒定,因此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();
Run Code Online (Sandbox Code Playgroud)

在这里,它将并行性移动到主文件夹枚举.上面的大多数变化也适用.


Chi*_*ata 16

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

HashSet<FileData> GetPast60(string dir)
{
    DateTime retval = DateTime.Now.AddDays(-60);
    HashSet<FileData> oldFiles = new HashSet<FileData>();

    FileData [] files = FastDirectoryEnumerator.GetFiles(dir);
    for (int i=0; i<files.Length; i++)
    {
        if (files[i].LastWriteTime < retval)
        {
            oldFiles.Add(files[i]);
        }
    }    
    return oldFiles;
}
Run Code Online (Sandbox Code Playgroud)

编辑

因此,根据以下评论,我决定在此处以及我能想到的其他建议解决方案的基准.这是足够有趣的是,EnumerateFiles似乎在性能在C#FindNextFile,而EnumerateFilesAsParallel是迄今为止最好的.

但是,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() + dirInfo.EnumerateFiles("*.*", SearchOption.TopDirectoryOnly).Count();
}


private static int GetPast60(string dir)
{
    return FastDirectoryEnumerator.GetFiles(dir, "*.*", SearchOption.AllDirectories).Length;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 在那篇文章中没有提到`EnumerateFiles`,**.** (2认同)

Mat*_*att 6

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

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

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