GC Unterbrechungen und TPL

9

Ich habe einen WCF-Dienst. Während der Arbeit des Dienstes muss er zwei Webdienste aufrufen. Es gibt also einen ähnlichen Code:

%Vor%

Meistens funktioniert das OK, aber gelegentlich sah ich Spitzen in der Ausführungszeit, wo die erste Aufgabe ein paar Sekunden dauerte, um überhaupt zu beginnen. Als ich auf perfmon schaute, wurde mir klar, dass dies genau der Zeitpunkt war, als GC stattfand. Anscheinend war GC eine höhere Priorität, als meine Aufgaben zu erledigen. Dies ist nicht akzeptabel, da die Latenz für mich sehr wichtig ist und ich bevorzuge, dass GC zwischen Anfragen und nicht mitten in einer Anfrage stattfindet.

Ich habe versucht, einen anderen Weg zu gehen, und anstatt meine eigenen Aufgaben zu drehen, habe ich WebClient.DownloadStringTask .

%Vor%

Das hat nicht geholfen; Der GC läuft jetzt, nachdem die Aufgabe begonnen hat, aber vor der Fortsetzung. Ich vermute mal, dass das System jetzt inaktiv ist, also ist es ein guter Zeitpunkt, GC zu starten. Nur kann ich mir die Latenz nicht leisten.

Die Verwendung von TaskCreationOptions.LongRunning, was dazu führt, dass der Scheduler Threads ohne Thread-Pool verwendet, scheint dies zu lösen, aber ich möchte nicht so viele neue Threads erstellen - dieser Code wird viel laufen (mehrmals pro Anfrage).

Was ist der beste Weg, um dieses Problem zu lösen?

    
Doron Yaacoby 17.07.2012, 08:07
quelle

5 Antworten

3

Lassen Sie mich zuerst einige Missverständnisse beseitigen, die auf dieser Seite zu sehen sind:

  • GC findet nicht im Leerlauf statt. Es findet statt, wenn es aufgrund einer fehlgeschlagenen Zuweisung (neu), GC.Collect oder OS-Speicherdruck
  • ausgelöst wird
  • Der GC kann Anwendungsthreads stoppen. Es läuft nicht gleichzeitig (zumindest für eine bestimmte Zeit)
  • "% time in GC" ist ein Zähler, der zwischen GCs nicht wechselt, was bedeutet, dass Sie möglicherweise einen veralteten Wert sehen
  • Async-Code hilft nicht bei GC-Problemen. Tatsächlich erzeugt es mehr Müll (Tasks, IAsyncResult und wahrscheinlich noch etwas anderes)
  • Das Ausführen von Code in dedizierten Threads verhindert nicht, dass sie gestoppt werden

Wie behebt man das?

  1. Weniger Müll erzeugen. Einen Speicherprofiler anfügen (JetBrains ist einfach zu verwenden) und sehen, was Abfall und was auf deinem Haufen
  2. erzeugt
  3. Reduzieren Sie die Heap-Größe, um die Pausenzeit zu reduzieren (Ein 3-GB-Heapspeicher ist wahrscheinlich auf Caching zurückzuführen? Vielleicht den Cache verkleinern?)
  4. Starten Sie mehrere ASP.NET-Sites mit derselben App, verknüpfen Sie GC-Benachrichtigungen, um einen GC zu erkennen, und nehmen Sie einige der IIS-Sites aus der Lastenausgleichsrotation, während sie einen GC haben ( Ссылка )

Sie werden feststellen, dass es keine einfache Lösung gibt. Ich kenne keine, aber wenn das Problem von GC verursacht wird, wird das Problem durch eine der oben genannten behoben.

    
usr 23.07.2012 09:00
quelle
1

Ich weiß, dass Ihre Frage etwas mit GC zu tun hat, aber ich würde gerne zuerst über die asynchrone Implementierung sprechen und dann sehen, ob Sie immer noch die gleichen Probleme haben werden.

Wenn Sie den Beispielcode Ihrer ursprünglichen Implementierung nicht mehr benötigen, verschwenden Sie drei CPU-Threads, die gerade auf I / O warten:

  • Der erste Thread, der verschwendet wird, ist der ursprüngliche WCF-E / A-Thread, der den Aufruf ausführt. Es wird von Task.WaitAll blockiert, während die untergeordneten Aufgaben noch ausstehen.
  • Die anderen zwei Threads, die verschwendet werden, sind die Threadpool-Threads, mit denen Sie die Aufrufe von Service1 und Service2
  • ausführen

Während der gesamten I / O zu Service1 und Service2 sind die drei CPU-Threads, die Sie verschwenden, nicht dazu geeignet, andere Arbeiten auszuführen, und der GC muss sie umgehen.

Meine erste Empfehlung wäre daher, Ihre WCF-Methode selbst so zu ändern, dass sie das von der WCF-Laufzeit unterstützte Muster des asynchronen Programmiermodells (APM) verwendet. Dadurch wird das Problem des ersten vergeudeten Threads gelöst, indem der ursprüngliche WCF-E / A-Thread, der in Ihre Dienstimplementierung aufgerufen wird, sofort in seinen Pool zurückgegeben wird, um andere eingehende Anforderungen bearbeiten zu können. Sobald Sie das getan haben, möchten Sie als nächstes die Aufrufe von Service1 und Service2 asynchron von der Client-Perspektive aus durchführen. Das würde eines von zwei Dingen beinhalten:

  1. Generierung asynchroner Versionen ihrer Vertragsschnittstellen, die wiederum APM BeginXXX / EndXXX verwenden, das WCF auch im Clientmodell unterstützt.
  2. Wenn dies einfache REST-Services sind, mit denen Sie gerade sprechen, haben Sie folgende andere Async-Optionen:
    • WebClient::DownloadStringAsync implementation ( WebClient ist nicht meine favorisierte API persönlich)
    • HttpWebRequest::BeginGetResponse + HttpWebResponse::BeginGetResponseStream + HttpWebRequest::BeginRead
    • Gehen Sie mit der neuen Web-API auf den neuesten Stand. HttpClient

Wenn Sie all dies zusammenfügen, gäbe es keine überflüssigen Threads, während Sie auf eine Antwort von Service1 und Service2 in Ihrem Service warten. Der Code würde ungefähr so ​​aussehen, wenn Sie eine WCF-Client-Route genommen haben:

%Vor%

Sobald Sie alles an Ort und Stelle haben, werden Sie technisch gesehen KEINE CPU-Threads ausführen, während die I / O mit Service1 und Service2 aussteht. Dabei gibt es keine Threads für den GC, die sich sogar darum kümmern müssen, die meiste Zeit zu unterbrechen. Die einzige Zeit, in der die eigentliche CPU-Arbeit stattfindet, ist die ursprüngliche Planung der Arbeit und dann die Fortsetzung auf ContinueWhenAll, wo Sie alle Ausnahmen behandeln und die Ergebnisse massieren.

    
Drew Marsh 19.07.2012 23:51
quelle
0

Ich empfehle Ihnen, Drews Antwort zu überdenken. Ein voll-asynchrones System wäre ideal.

Wenn Sie jedoch weniger Code ändern möchten, können Sie FromAsync anstelle von StartNew verwenden (dies erfordert asynchrone Proxys für Service1 und Service2 ):

%Vor%

Dies reduziert die Anzahl der Threadpool-Threads pro WaitAll von 3 auf 1. Sie sind immer noch nicht am Ideal (0), aber Sie sollten eine Verbesserung sehen.

    
Stephen Cleary 22.07.2012 21:37
quelle
0

Vielleicht möchten Sie dies versuchen, aber es könnte das Problem nur ein wenig auf den Kopf stellen:

%Vor%

Wir empfehlen: Ссылка

    
Roger Johnson 26.07.2012 04:52
quelle
0

Wenn Sie viele Webanforderungen ausführen, laden Sie eine Menge temporärer Objekte in den verwalteten Heap. Während der Heap wächst, wird der GC versuchen, etwas Speicher freizugeben, bevor ein neues GC-Segment zugewiesen wird. Dies ist der Hauptgrund, warum Sie sehen, GCs während Sie arbeiten.

Jetzt kommt der interessante Teil: Ihr GC Heap ist bereits 3 GB groß und Sie haben einige Web-Anfragen mit kurzlebigen Objekten zusätzlich auf dem GC-Heap. Volle GCs benötigen viel Zeit, um Ihr sicherlich komplexes Objektdiagramm (alle 3 GB) für tote Objekte zu durchlaufen. In einem solchen Hochdurchsatz-Szenario, in dem Sie für jede Anfrage eine erhebliche Menge an Temp-Daten über die Leitung erhalten, erzwingen Sie viele GCs.

An dieser Stelle sind Sie an die GC gebunden: Die Anwendungsleistung unterliegt nicht mehr Ihrer Kontrolle. Sie können dies normalerweise durch sorgfältiges Design Ihrer Datenstrukturen und Zugriffsmuster beheben, aber die GC-Zeiten werden größtenteils (ich denke & 95%) Ihre Anwendungsleistung dominieren.

Es gibt keinen einfachen Ausweg. Um die GC-Segmente kleiner zu machen, indem Sie Ihren Gesamtspeicherverbrauch überprüfen, kann es schwierig sein, wenn es ein großes komplexes System ist. Eine Alternative könnte darin bestehen, einen zusätzlichen Prozess (nicht eine neue Anwendungsdomäne, da der Anwendungsmanager überhaupt keine Anwendungsdomänen kennt) zu generieren und dort Ihre kurzlebigen Objekte in Ihren Webanforderungen zu erstellen. Dann können Sie aus dieser Unordnung herauskommen, wenn Sie in Ihrem kleinen Prozess eine sinnvolle Antwort berechnen können, die dann von Ihrem großen Serverprozess verwendet wird. Wenn Ihr Prozess eine gleiche Menge von temporären Daten erstellt, wie Ihre ursprünglichen Webanfragen, sind Sie wieder auf Platz eins und Sie haben nichts gewonnen.

Es kann hilfreich sein, Objekte aus früheren Webanforderungen wiederzuverwenden und einen Pool von Objekten bereit zu halten, um die Anzahl der Zuordnungen zu reduzieren.

Wenn Sie viele identische Strings in Ihrem Prozess-Heap haben, kann es hilfreich sein, sie zu puffern, wenn sie nie wieder freigegeben werden. Dies könnte helfen, Ihr Objektdiagramm zu vereinfachen.

    
Alois Kraus 26.07.2012 12:32
quelle