如何并行化搜索文件夹的过程?
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;
}
呼,我必须给出我的答案。
@aepot 评论说删除一行会使
var z = foundFiles.Distinct().ToList();代码运行速度提高 5 倍。为什么会这样,工作的加速度从何而来?事实是,当处理器执行这行代码时,新的数据部分没有新的磁盘访问。也就是说,驱动器处于空闲状态。
我们删除了这一行——我们得到了更频繁的 IO 请求。
在这种情况下,并行化可以提供帮助:一个线程读取了部分数据并处理了一段时间,而另一个线程读取了数据。然后第二个线程处理数据,第一个线程再次读取。因此,数据处理花费的时间越长,增加线程数(并行化)的意义就越大。
但不要将此与直接并行访问驱动器混淆!如果多个线程正在空闲读取数据(并且没有以任何方式使用它),那么它们将简单地在队列中等待。
我相信生产者-消费者模式是解决长数据处理问题的经典解决方案:一个线程从驱动器中读取数据并将其添加到某个集合中,另一个线程从该集合中获取数据并处理它。同时,没有线程等待轮到它使用磁盘。
我有一个相当快的 SSD。无论我如何尝试并行化搜索,它仍然只吃一个处理器,也就是说,所有工作都由驱动程序在一个线程中的某个地方完成,而我在应用程序中的线程只是挂起并等待这项工作的结果。但是由于异步和产生线程的开销非常大,因为我搜索了整个磁盘
C:\,所以该方法的执行被延迟并且需要更长的时间。我还尝试拆分目录扫描和获取文件。在第一次我同步执行到一个字符串数组中,然后迭代完成的数组以搜索所有可用目录中的文件。也没有性能提升,而是由于相同的异步开销而导致性能下降。
因此,您的问题的答案是:由于文件系统驱动程序的特殊性,并行化在这里毫无用处。
综上所述,结论是:值得关注优化,而不是并行化,因为优化比并行化可以带来更大的提升。
问题中您的方法的主要问题是它首先完成然后只返回到
IEnumerable调用方法。也就是说,所有递归都将首先工作,然后只有您可以迭代结果。更准确地说,只有当所有.Concat. 您可以修复此问题并获取文件系统返回的结果。第二个问题是一个bug,一个未使用的变量
z,计算它的值需要生成一个数组,但是你对这个变量什么也不做,所以你可以简单地删除该行var z = foundFiles.Distinct().ToList();。第三个问题是该方法试图从已知不可访问的目录中获取文件。因此,该方法抛出的异常数量是理想情况下的两倍。
优化后的递归方法如下:
可以进一步优化什么?当我遇到递归方法时和往常一样——摆脱递归。我尝试了使用数组和迭代大型数组的机制——我没有得到太多的增长,但我只是从
Directory.EnumerateDirectories一个直接传递参数的方法中得到SearchOption它。也就是说,搜索任务的一半转移到了 .NET,然后增长变得切实可见,尽管并不出色。无递归实现:
而且方法本身也变得更容易看。
好吧,我测量了发布版本的性能。
开始了一个日志类。由于我只需要错误统计信息,我只计算它们,不会在任何地方显示它们。
您的问题中的方法在此处命名,
GetDirectoryFilesOriginal并且为了实验的纯度,我将其保持在中间不变,因此由于对目录的第一次访问尚未缓存并且可以工作,因此它似乎不会变慢慢点。在我的特殊情况下,情况并非如此。我得到了这个输出(带有代码优化的发布版本):
我不得不耐心等待你的方法结束。结果,全面优化将该方法加速了6.3 倍。
所以你的问题的解决方案不是并行化,而是优化。
(不是真正的答案/或根本不是答案)
使用 HDD 并行化工作是无用的练习。无论如何,您都依赖于硬盘驱动器的速度。而且线程多的情况下,任务的整体速度甚至会下降。这是因为多次读取和写入具有相同的优先级,并且您不断需要将每个线程的头部移动到不同的位置。移动头部 = 将时间浪费在额外的工作上。
在 SSD 的情况下,事情就不是那么清楚了。可能有理论上的提升,也可能有短期的提速,也可能根本没有提升。您不能只说“它会在 SSD 上运行得更快”,因为 ssd ssd 是不同的。拿一个中国的低级 ssd - 那里的写操作将是 70-80 兆字节(老实说,不是根据 CrystalDiskMark,它像呼吸一样躺着)。读操作——幸运。但大约 300-350(我希望)。
如果您使用中等价位的 SSD,他们会通过添加缓存来关闭恶心的铁的孔。因此,写入速度会很好,直到缓存被阻塞。然后会有悲伤。
如果您使用像三星 Evo 这样的高端固态硬盘 - 所有固态硬盘都会有 + - 诚实的速度。
我确定还有一些我不知道的其他功能。
也可以有不同类型的 RAID。在那里,并行记录速度的变化也取决于袭击的类型。
无关:
根本不要使用 CrystalDiskMark。这是一个令人作呕的基准测试,它并没有从“一般”这个词中显示出对 SSD 工作的客观评估。
概括:
一般来说,即使我们编写了一个并行化算法,在某些情况下我们也会得到一个减速而不是期望的加速。
为了确定这是否值得做,您需要在不同的任务上使用不同价格类别的不同 SSD 进行大量测试。只有这样,才能清楚地参与到特定任务中是否有意义,以及编写什么拐杖,以免导致最终用户速度减慢。
就个人而言,原则上我根本不会并行 I/O 任务。
我会把它作为选项之一扔掉
代码没有运行,所以你自己试试。