RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1234880
Accepted
Radzhab
Radzhab
Asked:2022-01-23 01:39:47 +0000 UTC2022-01-23 01:39:47 +0000 UTC 2022-01-23 01:39:47 +0000 UTC

如何并行递归搜索文件夹中的文件?

  • 772

如何并行化搜索文件夹的过程?

public  IEnumerable<string> GetDirectoryFiles(string rootPath, string patternMatch, SearchOption searchOption)
{
    
    var foundFiles = Enumerable.Empty<string>();

    if (searchOption == SearchOption.AllDirectories)
    {
        try
        {
            IEnumerable<string> subDirs = Directory.EnumerateDirectories(rootPath);
            foreach (string dir in subDirs)
            {
                foundFiles = foundFiles.Concat(GetDirectoryFiles(dir, patternMatch, searchOption)); // Add files in subdirectories recursively to the list
            }
        }
        catch (UnauthorizedAccessException ex) { LoggingExtensions.WriteDebug(ex.Message); }
        catch (PathTooLongException ex) { LoggingExtensions.WriteDebug(ex.Message); }
    }

    try
    {
        var files = Directory.EnumerateFiles(rootPath, patternMatch);
        foundFiles = foundFiles.Concat(files); 
        var z = foundFiles.Distinct().ToList();
    }
    catch (UnauthorizedAccessException ex) { LoggingExtensions.WriteDebug(ex.Message); }
    return foundFiles;
}
c#
  • 4 4 个回答
  • 10 Views

4 个回答

  • Voted
  1. Alexander Petrov
    2022-01-23T18:57:02Z2022-01-23T18:57:02Z

    呼,我必须给出我的答案。

    @aepot 评论说删除一行会使var z = foundFiles.Distinct().ToList();代码运行速度提高 5 倍。

    为什么会这样,工作的加速度从何而来?事实是,当处理器执行这行代码时,新的数据部分没有新的磁盘访问。也就是说,驱动器处于空闲状态。

    我们删除了这一行——我们得到了更频繁的 IO 请求。

    在这种情况下,并行化可以提供帮助:一个线程读取了部分数据并处理了一段时间,而另一个线程读取了数据。然后第二个线程处理数据,第一个线程再次读取。因此,数据处理花费的时间越长,增加线程数(并行化)的意义就越大。

    但不要将此与直接并行访问驱动器混淆!如果多个线程正在空闲读取数据(并且没有以任何方式使用它),那么它们将简单地在队列中等待。


    我相信生产者-消费者模式是解决长数据处理问题的经典解决方案:一个线程从驱动器中读取数据并将其添加到某个集合中,另一个线程从该集合中获取数据并处理它。同时,没有线程等待轮到它使用磁盘。

    • 6
  2. Best Answer
    aepot
    2022-01-23T23:17:22Z2022-01-23T23:17:22Z

    我有一个相当快的 SSD。无论我如何尝试并行化搜索,它仍然只吃一个处理器,也就是说,所有工作都由驱动程序在一个线程中的某个地方完成,而我在应用程序中的线程只是挂起并等待这项工作的结果。但是由于异步和产生线程的开销非常大,因为我搜索了整个磁盘C:\,所以该方法的执行被延迟并且需要更长的时间。

    我还尝试拆分目录扫描和获取文件。在第一次我同步执行到一个字符串数组中,然后迭代完成的数组以搜索所有可用目录中的文件。也没有性能提升,而是由于相同的异步开销而导致性能下降。

    因此,您的问题的答案是:由于文件系统驱动程序的特殊性,并行化在这里毫无用处。

    综上所述,结论是:值得关注优化,而不是并行化,因为优化比并行化可以带来更大的提升。

    问题中您的方法的主要问题是它首先完成然后只返回到IEnumerable调用方法。也就是说,所有递归都将首先工作,然后只有您可以迭代结果。更准确地说,只有当所有.Concat. 您可以修复此问题并获取文件系统返回的结果。

    第二个问题是一个bug,一个未使用的变量z,计算它的值需要生成一个数组,但是你对这个变量什么也不做,所以你可以简单地删除该行var z = foundFiles.Distinct().ToList();。

    第三个问题是该方法试图从已知不可访问的目录中获取文件。因此,该方法抛出的异常数量是理想情况下的两倍。

    优化后的递归方法如下:

    public IEnumerable<string> GetDirectoryFiles(string rootPath, string patternMatch, SearchOption searchOption)
    {
        bool dirSuccess = true;
        if (searchOption == SearchOption.AllDirectories)
        {
            dirSuccess = false;
            IEnumerable<string> subDirs = Enumerable.Empty<string>();
            try
            {
                subDirs = Directory.EnumerateDirectories(rootPath);
                dirSuccess = true;
            }
            catch (UnauthorizedAccessException ex) { LoggingExtensions.WriteDebug(ex.Message); }
            catch (PathTooLongException ex) { LoggingExtensions.WriteDebug(ex.Message); }
    
            foreach (string dir in subDirs)
            {
                foreach (string path in GetDirectoryFiles(dir, patternMatch, searchOption))
                {
                    yield return path;
                }
            }
        }
    
        // нет смысла пытаться запрашивать файлы, если нет доступа к каталогу или возникла другая ошибка
        if (dirSuccess)
        {
            foreach (string path in Directory.EnumerateFiles(rootPath, patternMatch))
            {
                yield return path;
            }
        }
    }
    

    可以进一步优化什么?当我遇到递归方法时和往常一样——摆脱递归。我尝试了使用数组和迭代大型数组的机制——我没有得到太多的增长,但我只是从Directory.EnumerateDirectories一个直接传递参数的方法中得到SearchOption它。也就是说,搜索任务的一半转移到了 .NET,然后增长变得切实可见,尽管并不出色。

    无递归实现:

    public IEnumerable<string> GetDirectoryFilesFast(string rootPath, string patternMatch, SearchOption searchOption)
    {
        foreach (string file in Directory.EnumerateFiles(rootPath, patternMatch))
        {
            yield return file;
        }
        if (searchOption == SearchOption.AllDirectories)
        {
            IEnumerator<string> enumarator = Directory.EnumerateDirectories(rootPath, string.Empty, searchOption).GetEnumerator();
            while (true)
            {
                bool skip = true;
                try
                {
                    if (!enumarator.MoveNext())
                        break;
                    skip = false;
                }
                catch (UnauthorizedAccessException ex) { LoggingExtensions.WriteDebug(ex.Message); }
                catch (PathTooLongException ex) { LoggingExtensions.WriteDebug(ex.Message); }
    
                if (skip)
                    continue;
    
                foreach (string file in Directory.EnumerateFiles(enumarator.Current, patternMatch))
                {
                    yield return file;
                }
            }
        }
    }
    

    而且方法本身也变得更容易看。

    好吧,我测量了发布版本的性能。

    开始了一个日志类。由于我只需要错误统计信息,我只计算它们,不会在任何地方显示它们。

    public static class LoggingExtensions
    {
        public static int ErrorsCount { get; set; }
        public static void WriteDebug(string text) { ErrorsCount++; }
    }
    
    static void Main(string[] args)
    {
        Console.WriteLine("GetDirectoryFiles");
        DateTime date = DateTime.Now;
        int i = 0;
        LoggingExtensions.ErrorsCount = 0;
        foreach (string path in GetDirectoryFiles(@"C:\", "*.cs", SearchOption.AllDirectories)) { i++; }
        Console.WriteLine($"Found {i} files");
        Console.WriteLine($"{LoggingExtensions.ErrorsCount} exceptions thrown");
        TimeSpan elapsed = DateTime.Now - date;
        Console.WriteLine($"Elapsed {elapsed.TotalSeconds}s");
    
        Console.WriteLine("GetDirectoryFilesOriginal");
        date = DateTime.Now;
        i = 0;
        LoggingExtensions.ErrorsCount = 0;
        foreach (string path in GetDirectoryFilesOriginal(@"C:\", "*.cs", SearchOption.AllDirectories)) { i++; }
        Console.WriteLine($"Found {i} files");
        Console.WriteLine($"{LoggingExtensions.ErrorsCount} exceptions thrown");
        elapsed = DateTime.Now - date;
        Console.WriteLine($"Elapsed {elapsed.TotalSeconds}s");
    
        Console.WriteLine("GetDirectoryFilesFast");
        date = DateTime.Now;
        i = 0;
        LoggingExtensions.ErrorsCount = 0;
        foreach (string path in GetDirectoryFilesFast(@"C:\", "*.cs", SearchOption.AllDirectories)) { i++; }
        Console.WriteLine($"Found {i} files");
        Console.WriteLine($"{LoggingExtensions.ErrorsCount} exceptions thrown");
        elapsed = DateTime.Now - date;
        Console.WriteLine($"Elapsed {elapsed.TotalSeconds}s");
        Console.ReadKey();
    }
    

    您的问题中的方法在此处命名,GetDirectoryFilesOriginal并且为了实验的纯度,我将其保持在中间不变,因此由于对目录的第一次访问尚未缓存并且可以工作,因此它似乎不会变慢慢点。在我的特殊情况下,情况并非如此。

    我得到了这个输出(带有代码优化的发布版本):

    GetDirectoryFiles
    Found 2612 files
    287 exceptions thrown
    Elapsed 89,225523s
    GetDirectoryFilesOriginal
    Found 2612 files
    574 exceptions thrown
    Elapsed 440,9526969s
    GetDirectoryFilesFast
    Found 2612 files
    287 exceptions thrown
    Elapsed 69,9758348s
    

    我不得不耐心等待你的方法结束。结果,全面优化将该方法加速了6.3 倍。

    所以你的问题的解决方案不是并行化,而是优化。

    • 6
  3. Andrew_STOP_RU_AGRESSION_IN_UA
    2022-01-23T16:51:36Z2022-01-23T16:51:36Z

    (不是真正的答案/或根本不是答案)

    使用 HDD 并行化工作是无用的练习。无论如何,您都依赖于硬盘驱动器的速度。而且线程多的情况下,任务的整体速度甚至会下降。这是因为多次读取和写入具有相同的优先级,并且您不断需要将每个线程的头部移动到不同的位置。移动头部 = 将时间浪费在额外的工作上。

    在 SSD 的情况下,事情就不是那么清楚了。可能有理论上的提升,也可能有短期的提速,也可能根本没有提升。您不能只说“它会在 SSD 上运行得更快”,因为 ssd ssd 是不同的。拿一个中国的低级 ssd - 那里的写操作将是 70-80 兆字节(老实说,不是根据 CrystalDiskMark,它像呼吸一样躺着)。读操作——幸运。但大约 300-350(我希望)。

    如果您使用中等价位的 SSD,他们会通过添加缓存来关闭恶心的铁的孔。因此,写入速度会很好,直到缓存被阻塞。然后会有悲伤。

    如果您使用像三星 Evo 这样的高端固态硬盘 - 所有固态硬盘都会有 + - 诚实的速度。

    我确定还有一些我不知道的其他功能。

    也可以有不同类型的 RAID。在那里,并行记录速度的变化也取决于袭击的类型。


    无关:

    根本不要使用 CrystalDiskMark。这是一个令人作呕的基准测试,它并没有从“一般”这个词中显示出对 SSD 工作的客观评估。


    概括:

    一般来说,即使我们编写了一个并行化算法,在某些情况下我们也会得到一个减速而不是期望的加速。

    为了确定这是否值得做,您需要在不同的任务上使用不同价格类别的不同 SSD 进行大量测试。只有这样,才能清楚地参与到特定任务中是否有意义,以及编写什么拐杖,以免导致最终用户速度减慢。


    就个人而言,原则上我根本不会并行 I/O 任务。

    • 5
  4. tym32167
    2022-01-23T02:28:17Z2022-01-23T02:28:17Z

    我会把它作为选项之一扔掉

    if (searchOption == SearchOption.AllDirectories)
    {
        IEnumerable<string> subDirs = Directory.EnumerateDirectories(rootPath);
        return subDirs.Concat(subDirs.AsParallel().SelectMany(dir => GetDirectoryFiles(dir, patternMatch, searchOption)));
    }   
    

    代码没有运行,所以你自己试试。

    • 4

相关问题

  • 使用嵌套类导出 xml 文件

  • 分层数据模板 [WPF]

  • 如何在 WPF 中为 ListView 手动创建列?

  • 在 2D 空间中,Collider 2D 挂在玩家身上,它对敌人的重量相同,我需要它这样当它们碰撞时,它们不会飞向不同的方向。统一

  • 如何在 c# 中使用 python 神经网络来创建语音合成?

  • 如何知道类中的方法是否属于接口?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    表格填充不起作用

    • 2 个回答
  • Marko Smith

    提示 50/50,有两个,其中一个是正确的

    • 1 个回答
  • Marko Smith

    在 PyQt5 中停止进程

    • 1 个回答
  • Marko Smith

    我的脚本不工作

    • 1 个回答
  • Marko Smith

    在文本文件中写入和读取列表

    • 2 个回答
  • Marko Smith

    如何像屏幕截图中那样并排排列这些块?

    • 1 个回答
  • Marko Smith

    确定文本文件中每一行的字符数

    • 2 个回答
  • Marko Smith

    将接口对象传递给 JAVA 构造函数

    • 1 个回答
  • Marko Smith

    正确更新数据库中的数据

    • 1 个回答
  • Marko Smith

    Python解析不是css

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5