RAM密集型C#进程在几个小时后变慢

c# html-agility-pack memory-management multithreading performance

我在负责连续解析HTML页面的服务器上运行C#进程(服务)。它依赖于HTMLAgilityPack。症状是随着时间的推移变得越来越慢。

当我启动该过程时,它处理n页/秒。几个小时后,速度降至n / 2页/秒左右。几天后它可以降到n / 10。这种现象已被多次观察到并且具有相当的确定性。任何时候重新启动进程都会恢复正常。

非常重要的是:我可以在同一个进程中运行其他计算并且不会减慢速度:我可以随时使用我想要的任何内容达到100%CPU。这个过程本身并不慢。只有HTML解析才会变慢。

我可以用最少的代码重现它(实际上原始服务中的行为有点极端,但这段代码仍然会重现行为):

public static void Main(string[] args) {
    string url = "https://en.wikipedia.org/wiki/History_of_Texas_A%26M_University";
    string html = new HtmlWeb().Load(url).DocumentNode.OuterHtml;
    while (true) {
        //Processing
        Stopwatch sw = new Stopwatch();
        sw.Start();
        Parallel.For(0, 10000, i => new HtmlDocument().LoadHtml(html));
        sw.Stop();
        //Logging
        using(var writer = File.AppendText("c:\\parsing.log")) {
            string text = DateTime.Now.ToString() + ";" + (int) sw.Elapsed.TotalSeconds;
            writer.WriteLine(text);
            Console.WriteLine(text);
        }
    }
}

使用此最小代码,这将显示速度(每秒页数),作为自进程启动以来经过的小时数的函数:

在此处输入图像描述

每个明显的原因都被排除在外:

  • HTML页面更大或不同(在最小代码中它是同一页面)
  • 完整RAM:该过程在32 GB上使用大约500 MB
  • 其他进程使用CPU或RAM

它可能是关于RAM和内存分配的东西。我知道HTMLAgilityPack会进行大量的小对象内存分配(HTML节点和字符串)。很明显,内存分配和多线程不能很好地协同工作。但我不明白这个过程会变得越来越慢。

您是否知道有关CLR或Windows的任何内容可能导致某些RAM密集型(许多分配)处理变得越来越慢?比如惩罚以某种方式进行内存分配的线程?

一般承认的答案

我注意到使用HTMLAgilityPack的类似行为。

我发现,当一个yield的数据开始空间泄漏时,编译器生成的类上的本地变量会开始引起问题。因为没有代码可用...... bla bla,这是我的急救工具包

  1. 确保设置正确的策略 ,在app.config中更改GC集合状态将有助于碎片化。
  2. 当你不需要它们时,确保你是空的,只要你不需要它们,不要等待范围清理你的内存,因为IEnumerables在调用方法和方法范围的范围内被调用,并且可以存活更长时间比你想象的!在ILSpy中打开您的代码并查看<> d__0(0)生成的类。您将看到生成的内容,如d __。X = X;在这种情况下,X可以保存片段或整个页面。
  3. 您的本地变量将被提升到堆,因为如果它们不存在,则无法在IEnumable迭代中访问它们。
  4. 锁定开始成为一个问题,大型物品在你的第4代ram中淹没,它们正在开始阻止GC。 GC正在暂停您的线程以执行垃圾收集。
  5. HTMLAgility最糟糕的事情是它的片段最终成为一个真正的问题

    我很确定当你开始考虑HTML片段的范围时,你会发现事情会很顺利。看看你在SOS中使用WinDbg的执行并转储内存并查看。

怎么做。

  1. 打开WinDebug,按F6并附加到进程(在字段中输入进程ID,然后按确定)
  2. 然后输入,将执行加载到你的记忆中

    .loadby sos clr

  3. 然后进入

    !dumpheap -stat

然后,您将获得在您的应用程序中分配的内存项目,其中包含内存地址和按类型分组的大小,并从低标题到高标题排序,您将看到类似System.String []的内容,前面有一个masive数字,那就是你想先调查的后缀。

现在看看谁可以输入

!dumpheap -mt <heap address>

并且您将看到使用该内存表(MT)的地址以及它使用的ram的大小。

现在它变得有趣了,而不是你输入你输入的x100行yode

!dumpheap -mt <heap address>

它将打印的是分配内存的文件和代码行,编译器生成的类以及导致您悲伤的变量以及它所拥有的字节。

这就是所谓的“生产调试”,如果你有权访问服务器,我认为你有。

希望得到帮助,

沃尔特




许可下: CC-BY-SA with attribution
不隶属于 Stack Overflow
这个KB合法吗? 是的,了解原因
许可下: CC-BY-SA with attribution
不隶属于 Stack Overflow
这个KB合法吗? 是的,了解原因