【问题标题】:for loop enters 0% cpu hangfor 循环进入 0% cpu 挂起
【发布时间】: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 电话。

标签: c# .net for-loop clr


【解决方案1】:

至于不同。
可能不会比全部完成然后单个 LINQ 更快,但它应该更少内存。

using (StreamReader sr = new StreamReader("TestFile.txt"))
{
   String line;
   String path;
   HashSet<string> paths = HashSet<string>(StringComparer.OrdinalIgnoreCase);
   // Read and process lines from the file until the end of
   // the file is reached.
   while ((line = sr.ReadLine()) != null)
   {
       Console.WriteLine(line);
       path = Path.GetDirectoryName(line);
       if(!String.IsNullOrEmpty(path)) paths.Add(path.Trim());
   }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-19
    • 1970-01-01
    • 1970-01-01
    • 2017-12-16
    • 1970-01-01
    • 2011-09-30
    相关资源
    最近更新 更多