Процесс интенсивного использования C # в ОЗУ замедляется через несколько часов - PullRequest
0 голосов
/ 11 мая 2018

Я запускаю процесс (службу) C # на сервере, который отвечает за постоянный анализ HTML-страниц.Он опирается на HTMLAgilityPack.Симптом заключается в том, что с течением времени он становится все медленнее и медленнее.

Когда я запускаю процесс, он обрабатывает n страниц / с.Через несколько часов скорость снижается примерно до н / 2 стр / с.Через несколько дней оно может снизиться до n / 10.Явление наблюдалось много раз и является довольно детерминированным.Каждый раз, когда процесс перезапускается, все возвращается в нормальное состояние.

Очень важно: я могу выполнять другие вычисления в том же процессе, и они не замедляются: я могу достичь 100% ЦП с любым, что захочу в любое время.Сам процесс не медленный.Только разбор 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);
        }
    }
}

При использовании этого минимального кода отображается скорость (страниц в секунду) как функция количества часов, прошедших с начала процесса:

enter image description here

Исключены все очевидные причины:

  • HTML-страницы больше или отличаются (в минимальном коде это та же страница)
  • полная RAM: процесс использует около 500МБ на 32 ГБ
  • другие процессы используют ЦП или ОЗУ

Это может быть что-то с ОЗУ и выделением памяти.Я знаю, что HTMLAgilityPack выделяет много памяти для небольших объектов (узлы и строки HTML).Ясно, что распределение памяти и многопоточность не работают хорошо вместе.Но я не понимаю, как этот процесс может становиться все медленнее и медленнее.

Знаете ли вы что-нибудь о CLR или Windows, которые могут вызывать замедление и интенсивную обработку большого количества ОЗУ (много выделений) имедленнее? Как, например, штрафовать потоки, делающие выделение памяти определенным образом?

1 Ответ

0 голосов

Я заметил похожее поведение при использовании пакета HTMLAgilityPack.

Я обнаружил, что когда один из данных приводит к пробелу локальных переменных в сгенерированных компилятором классах, которые начинают вызывать проблемы. Поскольку код недоступен, вот мой аптечка первой помощи.

  1. Убедитесь, что вы установили правильную стратегию , изменение стратегии сбора GC в app.config поможет фрагментации.
  2. Убедитесь, что вы обнуляете вещи, когда они вам не нужны, как только они вам не нужны, не ждите, пока область очистит вашу память, так как IEnumerables вызывается в вызывающем методе и области видимости переменных метода и может живи намного дольше чем ты думаешь! Откройте ваш код в ILSpy и посмотрите на сгенерированные классы <> d__0 (0) . Вы увидите такие вещи, как d __. X = X; в этом случае X может содержать фрагмент или целую страницу.
  3. Ваши локальные переменные перемещаются в кучу, так как они не доступны в итерациях IEnumable, если их там нет.
  4. Блокировка начинает становиться проблемой, у вашего барана 4-го поколения появляются большие предметы, которые фактически начнут блокировать ГХ. GC приостанавливает ваши потоки, чтобы иметь возможность выполнять сборку мусора.
  5. Хуже всего то, что HTMLAgility - это фрагментов, которые в конечном итоге становятся реальной проблемой

    Я совершенно уверен, что когда вы начнете рассматривать область действия своих фрагментов HTML, вы обнаружите, что все пойдет хорошо. Посмотрите на ваше выполнение, используя WinDbg в SOS , сделайте дамп памяти и посмотрите.

Как это сделать.

  1. открыть WinDebug, нажать F6 и прикрепить к процессу (введите идентификатор процесса в поле и нажмите ок)
  2. затем загрузите выполнение в вашу память, введя

    .loadby sos clr
    
  3. затем введите

    !dumpheap -stat
    

Затем вы получите элементы памяти, выделенные в вашем приложении, с адресом памяти и размером, сгруппированным по типу и отсортированным от низкого заголовка к высокому заголовку, вы увидите что-то вроде System.String [] с огромным числом перед это то, что вы хотели бы исследовать в первую очередь.

Теперь, чтобы узнать, у кого это есть, вы можете набрать

!dumpheap -mt <heap address>

И вы увидите адреса, которые используют эту таблицу памяти (MT) и размер оперативной памяти, которую она использует.

Теперь это становится интересным, вместо того, чтобы проходить строки кода x100, которые вы вводите

!gcroot <address>

то, что он напечатает, - это файл и строка кода, которые распределили память, класс, сгенерированный компилятором, и переменную, вызывающую ваше горе, а также байты, которые он содержит.

Это то, что можно назвать «производственной отладкой» и работает, если у вас есть доступ к серверу, который, я думаю, у вас есть.

...