Ich führe einen C # -Prozess (Dienst) auf einem Server durch, der dafür verantwortlich ist, HTML-Seiten kontinuierlich zu analysieren. Es beruht auf HTMLAgilityPack. Das Symptom ist, dass es mit der Zeit immer langsamer wird.
Wenn ich den Prozess starte, behandelt es n Seiten / s. Nach einigen Stunden sinkt die Geschwindigkeit auf etwa n / 2 Seiten / s. Es kann nach wenigen Tagen auf n / 10 gehen. Das Phänomen wurde viele Male beobachtet und ist ziemlich deterministisch. Jedes Mal, wenn der Prozess neu gestartet wird, sind die Dinge wieder normal.
Ganz wichtig: Ich kann andere Berechnungen im selben Prozess ausführen und sie werden nicht verlangsamt: Ich kann jederzeit 100% CPU mit allem erreichen, was ich will. Der Prozess selbst ist nicht langsam. Nur das HTML-Parsing wird langsamer.
Ich könnte es mit minimalem Code reproduzieren (eigentlich ist das Verhalten im ursprünglichen Dienst ein bisschen extremer, aber dennoch reproduziert dieses Stück Code das Verhalten):
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);
}
}
}
Mit diesem minimalen Code wird die Geschwindigkeit (Seiten pro Sekunde) als Funktion der Anzahl der Stunden seit dem Start des Prozesses angezeigt:
Alle offensichtlichen Ursachen wurden ausgeschlossen:
Es könnte etwas über RAM und Speicherzuweisung sein. Ich weiß, dass HTMLAgilityPack viele kleine Objekte Speicherzuweisung (HTML-Knoten und Zeichenfolgen). Es ist klar, dass Speicherzuweisung und Multithreading nicht gut zusammen funktionieren. Aber ich verstehe nicht, wie der Prozess immer langsamer werden kann.
Kennen Sie etwas über die CLR oder Windows, das eine RAM-intensive Verarbeitung (viele Zuweisungen) verursachen könnte, die immer langsamer wird? Wie zum Beispiel bestrafen Sie Threads, die Speicherzuweisungen in einer bestimmten Weise tun?
Ich habe mit dem HTMLAgilityPack ein ähnliches Verhalten festgestellt.
Ich habe festgestellt, dass, wenn die Daten eines Ertrags beginnen, lokale Variablen im Compiler zu löschen, generierte Klassen anfangen, Probleme zu verursachen. Da kein Code verfügbar ist, ist hier mein Erste-Hilfe-Kasten.
Das Schlimmste an HTMLAgility ist, dass Fragmente ein echtes Problem darstellen
Ich bin mir ziemlich sicher, dass, wenn Sie den Umfang Ihrer HTML-Fragmente betrachten, Sie feststellen werden, dass die Dinge gut laufen werden. Schauen Sie sich Ihre Ausführung mit WinDbg in SOS an und machen Sie einen Speicherauszug Ihres Speichers und werfen Sie einen Blick darauf.
Wie geht das.
Laden Sie dann die Ausführung in Ihren Speicher, indem Sie eingeben
.loadby sos clr
dann eintreten
!dumpheap -stat
Sie würden dann die in Ihrer Anwendung zugewiesenen Speicherelemente mit der Speicheradresse und der Größe erhalten, die nach Typ gruppiert und von niedriger Kopfzeile zu hoher Kopfzeile sortiert sind. Sie werden so etwas wie System.String [] mit einer massiven Zahl davor sehen, das ist das Zeug, das Sie zuerst untersuchen möchten.
Jetzt können Sie sehen, wer das hat
!dumpheap -mt <heap address>
Außerdem sehen Sie die Adressen, die diese Speichertabelle (MT) verwenden, und die Größe des verwendeten RAM.
Jetzt wird es interessant, anstatt dass Sie durch x100 Codezeilen gehen, die Sie eingeben
!gcroot <address>
Was gedruckt wird, sind die Datei und die Codezeile, die den Speicher zugewiesen haben, die vom Compiler generierte Klasse und die Variable, die Sie betrübt, sowie die darin enthaltenen Bytes.
Dies ist, was man als "Produktions-Debugging" bezeichnen könnte und funktioniert, wenn Sie Zugriff auf den Server haben, den Sie vermutlich haben.