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

!gcroot <address>

它將打印的是分配內存的文件和代碼行,編譯器生成的類以及導致您悲傷的變量以及它所擁有的字節。

這就是所謂的“生產調試”,如果你有權訪問服務器,我認為你有。

希望得到幫助,

沃爾特



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow