【发布时间】:2014-02-08 01:58:33
【问题描述】:
我需要获取文件列表,将它们修剪到目录路径并返回一个不同的列表。在某些情况下,这可能会处理超过 500 万个文件。
我遇到了一个问题,由于我无法确定的原因,核心进程以 0% 的 CPU 使用率挂起。
var filePaths = File.ReadAllLines("list_of_files.txt");
// ...
blockSw.Restart();
int[] curCounter = new int[1];
Stopwatch groupSw = Stopwatch.StartNew();
Parallel.For(0, filePaths.LongLength, i =>
{
//Trim the filename, if it exists, off of every
// entry that we read out of the input file
filePaths[i] = (Path.GetDirectoryName(filePaths[i]));
//This can be used to safely report status
// little hack-y, though
lock (curCounter)
{
curCounter[0]++;
if (curCounter[0] % 100000 == 0)
{
Trace.WriteLine(curCounter[0].ToString() + " rows complete in "
+ groupSw.ElapsedMilliseconds
+ " ; total time: " + blockSw.ElapsedMilliseconds);
groupSw.Restart();
}
}
}
);
blockSw.Stop();
Trace.WriteLine("Completed path truncation in " + blockSw.ElapsedMilliseconds + "ms.");
然后输出看起来像这样:
100000 rows complete in 266 ; total time: 266
200000 rows complete in 239 ; total time: 507
300000 rows complete in 843 ; total time: 1351
400000 rows complete in 1058 ; total time: 2411
...
1100000 rows complete in 3480 ; total time: 11602
1200000 rows complete in 432 ; total time: 12036
1300000 rows complete in 342 ; total time: 12379
...
4800000 rows complete in 832 ; total time: 48617
4900000 rows complete in 377 ; total time: 48996
5000000 rows complete in 2841 ; total time: 51839
5100000 rows complete in 1285 ; total time: 53126
Completed path truncation in 148124ms.
注意最后两行... 53 秒完成所有内容,然后循环结束,我们坐下来等待约 90 秒。在 TaskManager 中观察进程,我可以看到它在这段时间内以 0% 的 CPU 空闲。
关于这里发生了什么或我可以在哪里寻找线索的任何线索?
列出文件路径的输入文件约为 400MB,在此过程中,TaskManager 报告的内存大小约为 900MB。在测试期间,有大量可用的物理 RAM 超过此数量。
取出循环内状态报告不会改变性能 - 在循环结束时,我们仍然会遇到约 90 秒的 0% CPU 使用率挂起。
我在使用标准 for 循环而不是 Parallel.For 时遇到同样的问题。
更新/解决方案
感谢克里斯、杰克和汉斯。有了 Chris 的输入,他无法重现,而 Hans 对 Break All 的建议,我能够缩小问题的范围。进一步调试,我发现实际问题是Path.GetDirectoryName 是罪魁祸首。虽然它几乎在每个文件路径上都以 0-15 毫秒的时间运行,但有几十个路径需要 2 分钟才能处理完。我注意到这些路径都包含 ~ 在其中。我仍然不清楚为什么它完全不使用 CPU 就这样做,但足以让我理解它是 Path 内部的,加速它的唯一方法是重新实现 GetDirectoryName。
【问题讨论】:
-
我无法在 LINQPad 中重现这一点。也许它与追踪有关?编辑:但是,我的只需要大约 9600 毫秒即可完成(即使是 400mb 的文件,有 51500000 行)。虽然文件中的文件路径都是相同的,所以也许有一些分支预测正在发挥作用。但远不及你的 53 秒。 EDITx2:或者更有可能,只是考虑并行循环的可用系统资源。
for循环对我来说花了大约 32 秒(虽然最后仍然不是随机延迟) -
你能附加一个调试器并找到它挂在哪里吗?
-
不是问这个问题的正确方法。发布代码的最简单版本。当您观察到挂起时,使用 Debug + Break All 并向我们展示 Call Stack 窗口内容的样子。启用非托管调试和 Microsoft 符号服务器。如果您在该调用堆栈中看到奇怪的命名 DLL,那么您已经找到了此挂起的主要来源。
-
这没有达到distinct list的规定要求。
-
感谢@Chris、@Jack 和@Hans。由于 Chris 无法在不同的数据集上重现,而 Hans 建议 Break All 并确认它实际上在做什么,我能够确认它实际上是一些长期运行的
Path.GetDirectoryName电话。