Laden...

Bestimmte Datenverarbeitung muss nach 200ms abgeschlossen sein, dauert aber manchmal deutlich länger

Erstellt von multitasker vor 10 Jahren Letzter Beitrag vor 10 Jahren 7.660 Views
M
multitasker Themenstarter:in
91 Beiträge seit 2008
vor 10 Jahren
Bestimmte Datenverarbeitung muss nach 200ms abgeschlossen sein, dauert aber manchmal deutlich länger

Hallo zusammen,
wir haben in unserer Firma Probleme bei einer Anwendung. Es handelt sich um eine Bildverarbeitungsanwendung (BVA) die auf einem Industrie-PC läuft. Auf dem Industrie-PC läuft Windows 7 64Bit und keine Anwendung bis auf diese BVA. Die BVA erhält jede 200ms ein 5MB großes Bild und startet eine Auswerteroutine. Die Auswerteroutine dauert ca. 10ms. Danach wird das Bild auf der Festplatte gespeichert und das Ergebnis der Auswerteroutine via TCP/IP an eine SPS versendet. Die SPS benötigt die Ergebnisse nach spätestens 200ms. Normalerweise dauert es 127ms +/-5ms bis die Daten nach dem Start der Bildaufnahme in der SPS ankommen. Nach nur wenigen Minuten kommt es vor, dass die TCP/IP Daten verzögert ankommen. Die Verzögerungen können bis zu 400ms sein.
In dem PC ist auch eine I/O Karte eingebaut. Zur Diagnose setzen wir an diversen Stellen in der BVA Ausgänge der I/O Karte. Ein Signal, bei dem Event das ausgelöst wird, wenn das Bild in der BVA verfügbar ist. Und ein weiteres, wenn die Auswerteroutine abgeschlossen ist. Die Signale zeigen nun, dass die Verzögerung an unterschiedlichen Stellen auftritt. Mal bevor das Bild in der BVA Anwendung ist und manchmal nach der Auswerteroutine und manchmal ist auch erst das TCP/IP Ergebnis verzögert. Per Wireshark haben wir ausgeschlossen, dass das Bild von der Kamera verzögert versendet wird.
Um mehr Informationen zu sammeln haben wir uns der .NET Logging Funktion bedient und an verschiedenen Stellen eine Logdatei mit Daten gefüllt. Es sind etwa 10 Einträge, die während der 10ms Auswerteroutine geloggt werden. Hier zeigt sich auch, dass die Verzögerungen an willkürlichen Stellen sind. Die Verzögerungen der Signale waren bei dem erhöhten Festplattenzugriff deutlich häufiger. Wenn keine besonderen Festplattenzugriffe, wie durch die Logdatei und auch das Speichern der Bilder stattfindet kommt es nur noch sehr – manchmal erst nach Tagen – vor, dass Signale verzögert ankommen.
Während die BVA Bilder auswertet werden keine Interaktionen an dem PC getätigt. Wenn dies gemacht wird, bspw. durch öffnen einer anderen Anwendung kommt es ebenfalls zu verzögerten Signalen.
Das Speichern der Bilder erfolgt in einem separaten Thread und dennoch haben wir Probleme mit dem Speichern. Für mich stellt sich nun die Frage, was genau im Hintergrund passiert und was es für Möglichkeiten hat durch Priorisierung etc. das System deterministischer und stabiler zu machen.

Gruß Jens

16.806 Beiträge seit 2008
vor 10 Jahren

Da Windows kein Echtzeitsystem ist und wir von recht geringen Zeiten sprechen ist das alles immer noch mehr oder minder "Echtzeit unter Windows" - alles völlig in der Toleranz.

Wieso legt ihr das Bild nicht in den RAM, um von dort aus zu arbeiten?
Wenn das Bild fertig ist kümmert sich eine Queue um das Speichern von RAM auf HDD. Und sollte die Routine nicht dahingehend umgeschrieben werden kann, könnte evtl. eine RAM-Disk helfen.

M
multitasker Themenstarter:in
91 Beiträge seit 2008
vor 10 Jahren

Hallo Abt,
das Bild wird schon im RAM gehalten, bis der Prüfroutine fertig ist und erst danach gespeichert. Die Speicherung dient dem Zweck, dass Bilder mit Schlechtkriterien dokumentiert sind.
Gibt es eine Möglichkeit die Systemprozesse zu niedriger zu priorisieren damit Festplattenzugriffe oder auch Benutzerinteraktionen mit dem Betriebssystem den BVA Prozess nicht ausbremsen?

Gruß

16.806 Beiträge seit 2008
vor 10 Jahren

Nicht in der Form; dafür ist Windows einfach nicht ausgelegt.
Dauerhaft 25MB/s (Overhead nicht mal beachtet)reine Anwendungslast ist für nen IPC jetzt auch nicht gerade "wenig", wenn die Hardware dafür nicht ausgelegt ist.

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo multitasker,

grundsätzlich stimme ich Abt zu. Unter Windows gibt es die nötige (Zeit-)Sicherheit eines Echtzeitsystems nicht.

Wenn es jedoch nur am GC liegt, gibt es ab .NET 4.5 möglicherweise Abhilfe:

The .NET Framework 4.5 includes new garbage collector enhancements for client and server apps, insbesondere der Titel des Abschnitt "My app cannot tolerate pauses during a certain time window" klingt vielversprechend.

herbivore

A
350 Beiträge seit 2010
vor 10 Jahren

Mal ne andere Frage :

Ich habe festgestellt, dass ich bei Zeitkritischen Anwendungen unter Mono in einer Linux Umgebung einfach besser fahre
Evtl. mal an eine Portierung gedacht ?

Grüße

W
872 Beiträge seit 2005
vor 10 Jahren

Speicherst Du das Bild in einem eigenen Thread?
Ich verarbeite ähnliche Mengen auf einer sehr großen Maschine (2*6 Core Maschine) und bei mir sehe ich abends im Log Spitzen von 1 Sekunde.
Du solltest Dich mal mit den Garbage Log Events beschäftigen, um zu validieren, dass der GC die Applikation ausbremst.

M
multitasker Themenstarter:in
91 Beiträge seit 2008
vor 10 Jahren

Erst einmal Danke für alle Antworten. Ich habe das Ganze etwas schlanker dargestellt als es eigentlich ist.
Wir haben eine Software, die wir selber entwickeln, die im Prinzip das Frontend für den Benutzer darstellt und auch die Ergebnisse der Bildverarbeitung versendet, etc. In unserer Software verwenden wir Bildverarbeitungsbibliotheken VisionPro der Firma Cognex. Deren aktuelle Version basiert auf .NET 4.0, weshalb wir derzeit auch unsere Software mit .NET 4.0 kompilieren können. Deshalb können wir aktuell nicht die Verbesserungen der GC aus dem .NET 4.5 nutzen. Leider ist auch keine Portierung auf ein anderes Betriebssystem möglich, da nur Windows unterstützt wird.
Die GarbegeLogEvents werde ich mir genauer anschauen.

W
872 Beiträge seit 2005
vor 10 Jahren

Deine Anwendung kann in einer höheren Version laufen als die externe DLL. (Stichwort side by side execution)
Wichtig ist vor allem, dass auf der Maschine die entsprechende Framework-Version laeuft.

4.221 Beiträge seit 2005
vor 10 Jahren

Kannst Du nicht möglichst viele Objekte mit IDisposable implementieren ? Im Dispose ein GC.SuppressFinalizeThis...

--> So musst Du zwar selber aufräumen... aber dafür kommt dir der GC nicht in die Quere.

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

S
322 Beiträge seit 2007
vor 10 Jahren

Hallo,

das neue GC-Property (SustainedLowLatency ) ist auch in dem Update 4.0.3 von .Net enthalten und somit mit v4.0 kompatibel.

KB2600211: Update 4.0.3 for Microsoft .NET Framework 4 – Runtime Update
Download Center: Update 4.0.3 für Microsoft .NET Framework 4 – Entwurfszeitupdate für Visual Studio 2010 SP1 (KB2600214)

Gruß
Steffen

P
1.090 Beiträge seit 2011
vor 10 Jahren

Kannst Du nicht möglichst viele Objekte mit IDisposable implementieren ? Im Dispose ein GC.SuppressFinalizeThis...

--> So musst Du zwar selber aufräumen... aber dafür kommt dir der GC nicht in die Quere.

Das solltest du auf keinen Fall machen, das unterdrückt nur das der Finalizer aufgerufen wird. Der brauch aber nur aufgerufen werden wenn das Objekt Dispose Implementiert. Damit machst du die Sache nur noch schlimmer.

Auch wenn man es eigendlich nie machen soll, könntest du villeicht nach dem Speichern und Senden der Daten GC.Collect aufrufen. Das verhindeert villeicht, das der GC während der Verarbeitung läuft.
Dir sollte da aber Bewust sei, das dadurch Optimierungen weg fallen die der GC intern macht.

Grundlegend musst du bei performance Optimierungen hingehen und genau messen wo die Zeit bleibt. Und dann mal den Quellcode der stellen hier Posten, dann kann man vielleicht mehr sagen.

MFG
Björn

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Palin,

ich sehe es genauso. Wenn es nicht möglich ist, die Verbesserungen der neuen GC-Implementierung zu nutzen, weil man eine ältere Framework-Version verwendet, dann sollte man versuchen, die Aktivitäten des GCs möglichst in die unkritischen Zeitfenster zu verlegen. Das wäre eine der wenigen Situation, in der GC.Collect Ausnahmsweise angebracht wäre.

Ich stimme dir auch darin zu, dass Dispose um seiner selbst willen zu implementieren und aufzurufen die Sache potenziell schlimmer macht. Dispose braucht man nur für (direkt oder indirekt vorhandene) _un_verwaltete Ressourcen (siehe Dispose implementieren und verwenden (IDisposable)). Bei ausschließlich verwalteten Ressourcen gewinnt man dadurch nichts, im Gegenteil.

Wenn es aber tatsächlich Objekte mit unverwalteten Ressourcen gibt, sollten diese Dispose implementieren und dann gilt wieder, dass man die Aufrufe von Dispose in den unkritischen Zeitfenstern durchführen sollte.

herbivore

P
1.090 Beiträge seit 2011
vor 10 Jahren

Wenn es aber tatsächlich Objekte mit unverwalteten Ressourcen gibt, sollten diese Dispose implementieren und dann gilt wieder, dass man die Aufrufe von Dispose in den unkritischen Zeitfenstern durchführen sollte.

Im ersten moment hätte ich da jetzt direkt "Ja Klar, gute Idee" gesagt.
Ich hab darüber jetzt noch mal ein bisschen nachgedacht und bin mir da wirklich nicht sicher.
Wenn nur beim GC.Collect der GC läuft bin ich mir zimmlich sicher, das es besser ist.

Ich fang vielleicht am Besten, damit an wie so ich darüber überhaupt noch mal nachgedacht habe.
Objekt erzugen -> Objekt benutzten -> Objekt Disposen ist für mich gut verständlicher Quellcode. Sollte man im allgemeinen so machen. Das Dispose da nicht aufzurufen, finde ich jetzt nicht so schön, dazu kommt noch das ich irgendwo noch einen Verweis auf das Objekt brauche damit ich Dispose aufrufen kann. Das macht meines erachtens den Quellcode schlechter und ich denke es wird mehr Speicher verbrauchen. Wenn auch nicht viel.

Höherer Speicher Verbrauch kann zu GC führen, der Worst Case ist hier wohl wenn es nötig wir das ein neuer Speicher Bereich Allociert wird. (Wenn ich das richtig im Kopf habe, läuft da auch immer ein kompletter GC.)

Grundlegend wenn da weitere GCs laufen ist es nicht so schon.
Man hat mehr Objekte, die Objekte sind langlebieger und wannderen in die 2. und 3. Generation, das sind eigendlich Punkte die man Vermeiden sollte.

Umgekert hat man natürlich nicht dem Code im Dispose, der da ausgeführt wir.
Ich kann nicht wirklich sagen was besser ist.

Was mir an dem Punkt eingefallen ist, ist mal zu schauen ob auch wirklich überall Dispose aufgerufen wird. Wenn die Codeanalyse richtig eingestellt ist sollte sie es eigendlich anzeigen, wo das nicht der Fall ist (Ist recht schnell gemacht). Wenn ich das jetzt richtig im Kopf habe werden alle Obekte bei denen der Finilizer noch aufgerufen werden muss beim GC erstmal in eine Liste gespeichert. Nach dem Collect die Liste Abgearbeitet um dann nochmal ein Collect zu machen.

Was den GC (4.0) angeht da hatte ich jetzt noch irgendwo im Hinterkopf das es da auch eine Server variante gibt. Google sagt mir ja und es sieht so aus als ob man sie auch auf den Client einschalten kann. Könnte man auch mal ausprobieren, ob es was bringt weiß ich nicht und mich da heute Arbend noch einzulesen wollte ich nicht machen (Gleich begint Big Bang Theory). Aber ich denke bei Bedarf Googlen und Lesen sollte, da jetzt keine Probleme geben.

Gegebenen fals bietet dir ein 32Bit Beriebsystem auch bessere Leistung, kann man im Notfall auch mal ausprobiere.

Und wie schon gesagt Messen, Messen und Messen.

Vielleicht gibt es auch einfacher Optimierungsmöglichkeiten. Nur ohne Code ist das immer schwer.

MFG
Björn

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Palin,

Objekt erzugen -> Objekt benutzten -> Objekt Disposen ist für mich gut verständlicher Quellcode. Sollte man im allgemeinen so machen.

im allgemeinen - wie gesagt - nur dann, wenn direkt oder indirekt unverwaltete (oder andere knappe) Ressourcen im Spiel sind, siehe dazu die Antwort an norman_timo in Dispose implementieren und verwenden (IDisposable).

Wenn man Objekte hat, die Dispose implementieren, sollte man Dispose im allgemeinen normalerweise so früh wie möglich aufrufen. Wenn möglich sollte man dafür die using-Anweisung verwenden. Dann kann der Aufruf von Dispose - auch wenn Exceptions auftreten - nicht vergessen werden.

Wenn ein Objekt Dispose nicht implementiert und das ist der bei weitem häufigere Fall, muss man Dispose natürlich auch nicht aufrufen (und kann es auch gar nicht).

Doch selbst wenn ein Objekt Dispose anbietet und das Dispose aufgerufen wird, sollte man sich klar sein, dass dadurch nicht der Speicher für das Objekt selbst freigegeben wird (Dispose ist dazu überhaupt nicht in der Lage; das kann nur der GC), siehe Dispose implementieren und verwenden (IDisposable), Antwort auf die Frage "Wird durch Dispose der Speicher für das Objekts selbst freigegeben?".

Das macht meines erachtens den Quellcode schlechter und ich denke es wird mehr Speicher verbrauchen.

Klar, wenn ein Objekt mit unverwalteten Ressourcen Dispose anbietet und dessen Dispose nicht aufgerufen wird, bleibt der Speicherverbrauch höher, bis das Dispose aufgerufen wird.

Der Vorschlag, im konkreten Fall Dispose nicht aufzurufen, basiert auf der Annahme, dass für einen Durchlauf genügen Hauptspeicher zur Verfügung steht und dieser Speicher zudem durch den während der unkritischen Phase nachgeholten Aufruf von Dispose und dem expliziten Aufrufs des GCs vor dem nächsten Durchlauf wieder freigegeben wird. Dass also durch das verzögerte Dispose kein zusätzlicher GC-Lauf nötig wird.

Diese Vermutung ist begründet, denn der Thread-Starter schrieb, dass die Laufzeitverlängerung erst nach einigen Durchläufen auftritt. Daher denke ich, dass im konkreten Fall - und nur darum ging es mir - mein Vorschlag eine gute Idee ist. Zumindest wenn der Speicherverbrauch und der dadurch momentan unkoordiniert dazwischenfunkende GC tatsächlich die Ursache für das Problem ist.

Was mir an dem Punkt eingefallen ist, ...

Gegen das, was du im zweiten Teil deines Beitrags geschrieben hast (beginnend mit diesem Zitat) habe ich keine Einwände. Das sollte multitasker alles mal untersuchen/prüfen.

herbivore

M
multitasker Themenstarter:in
91 Beiträge seit 2008
vor 10 Jahren

Hm, der explizite Aufruf von GC.Colllect wird in unserer Anwendung schon an einer unkritischen Stelle getan.

herbivore, du schreibst folgenden Satz.

Wenn es aber tatsächlich Objekte mit unverwalteten Ressourcen gibt, sollten diese Dispose implementieren und dann gilt wieder, dass man die Aufrufe von Dispose in den unkritischen Zeitfenstern durchführen sollte.

Du schreibst, dass Dispose auch an einer unkritischen Stelle aufgerufen werden soll. Weshalb ist dies auch notwendig?

P
1.090 Beiträge seit 2011
vor 10 Jahren

Ich bin mal so frei und Antwortre. Dispose gibt keinen Speicher frei, der wird erst vom GC freigegeben. Der Code der im Dispose aufgerufen wir hat also bis zu seiner Ausführung noch Zeit, also verschiebst du ihn zu einen Zeitpunkt der nicht in dem Kritischen Bereich liegt. Also kurz von deinem GC.Collect aufruf.

Wenn der GC wirklich immer nur läuft, wenn du ihn aufrufst. Ist es auch eine gute Idee, wenn nicht können sich daraus einige Nachteile ergeben. Hab ich oben ja erleuter.

Wenn ihr GC.Collect schon auf ruft, könnt ihr mal denn aufruf mal Weg machen. Der GC ist selbst optimierend, ein GC.Collect aufruf sorgt aber dafür das er diese Optimierungen nicht machen kann, vielleicht reicht das aus das der GC nicht in dem kritischen Zeitraum startet.

MFG
Björn

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

16.806 Beiträge seit 2008
vor 10 Jahren

Nen paar Sachen stimmen hier nicht.
Der GC läuft in einem extra Thread parallel zur Anwendung und überwacht, ob es Objekte gibt, die nicht mehr benötigt werden. Findet er etwas, dann zerstört der GC diese - ohne darauf zu achten, ob Dispose implementiert wird oder nicht.

Dispose ist eigentlich dazu da, dass unmanaged Ressourcen wie FileHandles auf alle Fälle freigegeben werden; egal ob Dispose aktiv oder passiv ausgeführt wird.
Wird Dispose nach Microsofts Vorstellung implementiert, dann muss man sich eigentlich nicht darum kümmern; sollte es aber in Form von using(), damit die Ressource so früh wie möglich freigegeben wird.
Aber trotzdem: gibt es keine Referenz mehr auf ein Objekt dann räumt der GC das Objekt auf, der Dekonstruktor wird aufgerufen und ebenfalls Dispose(false) ausgeführt (bezogen auf das MS Beispiel).
Resultat am Ende: Unmanaged Ressourcen werden auf alle Fälle freigegeben und es verbleiben keine Leichen.

Das automatische Abräumen funktioniert aber nicht immer, da der GC oft nicht erkennen kann ob tatsächlich kein anderes Objekt auf eine Ressource eines Objekts zugreift (zB Stream-Property o.ä.). Daher ist ein manuelles Dispose() an einer _passenden _Stelle unumgänglich.

P
1.090 Beiträge seit 2011
vor 10 Jahren

Nun wenn du Dispos wie in den MS beispiel Implementiert hast, hast du auch den Finilizer implementier und dann macht die Sache schon einen Unterschied für den GC.

Bei wenn auf objekte mit Finilizer keine Verweise mehr existieren und GC.SuppressFinilizer für die objekte nicht aufgerufen wurden. Sammelt der GC sie in einer Liste um dann nach dem Collect, den Finilizer aufzurufen. Erst dann werden die Objekte nach nochmaligen durchlauf des GC entfernt.

Solltest im Finilizer nicht das Dispose aufrufen. Werden auch die unmaged Ressorsen nicht Freigegeben. (Ja im MS beispiel wird es aufgerufen, ich hab aber schon genug implementierungen Gesehen die das nicht gemacht haben und sich dann gewundert haben wie so sie speicher Leks hatten, sie hatten doch Dispose implementiert 😉 )

Das Problem mit dem GC (4.0) ist das er bei einem Collect den Anwendungthread anhält um ein Collect durchführen zu können. Dort dabei schaut er auch ob auf den Objekten noch verweise sind. Überwacht, klingt einwenig als wenn er es die ganze Zeit mache, aber ich denke das hast du nicht gemeint.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo multitasker,

Du schreibst, dass Dispose auch an einer unkritischen Stelle aufgerufen werden soll. Weshalb ist dies auch notwendig?

offensichtlich, damit das Dispose zu einem Zeitpunkt ausgeführt wird, an dem es keinen Einfluss auf die Laufzeit des kritischen Teils hat. Das normale wäre ja, Dispose per using möglichst zeitnah durchzuführen. Nach meinem Vorschlag wird die Ausführung verschoben, bis die kritische Verarbeitung vorbei ist.

herbivore

M
multitasker Themenstarter:in
91 Beiträge seit 2008
vor 10 Jahren

Ok, ich habe nicht daran gedacht, dass Dispose auch eventuell längere Zeit benötigen kann (z.B. durch schließen von Dateien oder Datenbankverbindungen) und dass dies deshalb auch ein Grund sein kann, um den Aufruf von Dispose in ein unkritisches Zeitfenster zu legen.

Sehe ich es richtig, dass wenn ich den Speicher nicht unbedingt benötige, den mir der GC freigeben wird, dann brauche ich auch nicht manuell in einem unkritischen Zeitfenster vor dem GC.Collect Aufruf Dispose von Objekten aufrufen?
Im Prinzip geht es mir vor allem darum, dass der GC nicht selbstätig an ungewünschter Stelle das Wort übernimmt.

Wie ist es bzgl. der Häufigkeit des GC.Collect Aufrufes? In unserer Anwendung haben wir - wie schon gesagt - weniger Probleme mit mangelndem Speicher, sondern dass die Arbeit des GC in einem unkritischen Zeitfenster arbeitet und auch möglichst kurz. Denn dieses Zeitfenster kann u.Umst. recht klein sein. Ich denke es ist deshalb tendenziell eher besser den GC.Collect Aufruf häufiger durchzuführe, damit wenn er läuft er wenig zu tun hat. Unter häufiger stelle ich mir jede 1-2s vor.

16.806 Beiträge seit 2008
vor 10 Jahren

Im Prinzip geht es mir vor allem darum, dass der GC nicht selbstätig an ungewünschter Stelle das Wort übernimmt.

Komplett abschalten ist nicht. Du kannst ihm nur durch gezieltes Collect() zuvorkommen.
Ich bin mir aber nicht sicher, ob wir hier uns nicht an Dispose aufhängen oder ob eine andere Maßnahme besser ist.

Nen IPC ist nen Hardwarehuhn.. viel Ressourcen sind da einfach nicht da. Bis jetzt hast Du weder Hersteller noch Hardwarespezifisches genannt.
Wenn Du nen uralten Kontron hast oder nen BoxPC dann brauchen wir uns gar nicht mehr weiter unterhalten, weil das halt alte Krüppelkisten und gar nicht dafür ausgelegt sind. Ist nicht mit einem Server zu vergleichen.
Und dann mit so einem Zeit-kritischen Fall zu arbeiten und dann noch in einer Liga von 200ms ist jetzt halt nicht gerade so der Optimalfall für einen IPC - Dispose hin oder her.
Dauerhaft 25MB/s da ist es einfach vorprogrammiert, dass sich nen IPC irgendwann verschluckt.

Ich würde den Flaschenhals nicht an Dispose ausmachen, sondern an anderer Stelle.

P
1.090 Beiträge seit 2011
vor 10 Jahren

Hi multitasker,

GC.Collect häufig aufzurufen, macht die Sache nur schlimmer, das Aufrufen sorgt dafür das die Objekte in die höheren Generationen kommen. Das hat zur folge das die GC aufwändiger werden.

Grundlegen gilt rufe GC.Collect nie selber auf. (Ratschlag vom Programmierer des GC, der sollte es wissen). Es gibt ausnahmen, da sollte man aber wissen was man mach.

Wenn du dich da ein Arbeiten willst. Findest du in Grundlagen der Garbage Collection einen Start Punkt. (Aufs Framework achten).

Das mit dem Speicher nicht Freigeben, ist auch keine gute Idee. Der Rechner mag vielleicht genug haben. Aber die Anwendung nimmt sich ja nicht direkt denn ganzen Speicher, sondern allociert sich einen Speicherbereich, auf dem sie Arbeitet. Wenn dort kein Platz mehr ist wird erst ein vollständiger GC durchgeführt, wenn danach nicht genug Speicher frei ist, wird ein weiterer Speicherbereich allociert.

Denn besten weg den ich kenne, perfomaten Code zu schreiben.
Ist als erst mal sauberen gut lesbaren Code zu schreiben. (Ohne Optimierungen)
Also dafür sorgen, das Objekte eine möglichst kurze Lebensdauer haben, keine Aufrufe von Funktionen bei denen man nicht wirklich weiß was da geschieht (GC.Collect) und bekannte Performance Bremsen zu vermeide. z.B. das zusammen fügen von Strings in einer Schleife mit + statt dem StringBuilder.

Wenn es dann noch zu langsam ist geht man nicht hin und rät woran es liegen könnte. Sondern misst nach was wie viel Zeit braucht z.B. mit dem ANTS Proviler.
Dann nimmt man sich geanau 1ne stelle vor und Optimiert auf Performance, dann nach misst man wider. Wenn es besser geworden ist behält man die Änderung, wenn es gleich geblieben ist schaut man welche der Varianten sauberer ist und verwendet die Sauberere. Wenn es langsamer geworden ist, nimmt man die alte Variante.

Wie es langsamer werden kann wenn man es optimiert. Nun beim Build macht der Compiler Optimierungen (Beim Release Build mehr, des wegen auch beim Release testen). Bei manchen dieser Optimierungen ist der Compiler darauf angewiesen das der Code in einer gewissen Form vorliegt. Änderst du das durch deine Optimierung ist der Compiler nicht mehr in der Lage seine Optimierung zu machen.

Dir wird hier nur eins helfen. Messen, Messen, Messen.

Mit freundlichen Grüßen
Björn

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

16.806 Beiträge seit 2008
vor 10 Jahren

Palin, Deine Erklärungen sind gut aber so allgemeine Aussagen wie "+ operator ist schneller als StringBuilder" stimmen so nicht. Das kommt auf den Fall an, wie zum Beispiel auch C# Fundamentals: String Concat() vs. Format() vs. StringBuilder sehr gut zeigt.

Siehe auch das Fazit

•Concatenate (+) is great at ++concatenating ++several strings in one single statement.
•StringBuilder is great when you need to ++building ++a string across multiple statements or a loop.
•Format is great at performing ++formatting ++of strings in ways that concatenation cannot.

Und so Dinge wie "mal an der Ecke eine Performance-Verbesserung machen" und dann mal wieder an der endet nachher in unwartbarem Code.
Wenn wirklich zb TPL eingesetzt wird, um performanter zu werden - wenn nötig - dann sollte das in der Architektur auch Beachtung finden.

Aber um Micro-Performance geht es hier (eigentlich) nicht und wir sollten auch nicht vom Thema abkommen.

Letzten Endes hast Du aber eine Aussage gemacht, die definitiv korrekt ist, befolgt werden sollte und auch hier angebracht ist: einfacher Code ist meist viel effizienter als mit dem Messer in den Rücken durch die Brust ins Auge.

M
multitasker Themenstarter:in
91 Beiträge seit 2008
vor 10 Jahren

So recht du, Palin, mit deinen Aussagen hast, so muss ich auch Abt recht geben, denn wir kommen etwas vom Thema ab. Es geht nicht darum, dass die Software zu unperformant ist, sondern dass es ab und zu vorkommt, dass unsere Software ausgebremst wird. Dazu helfen sicherlich auch manche Tipps, wie z.B. kurze Lebensdauer von Variablen, aber solche Dinge sind bereits im Code berücksichtigt.

Bei dem IPC handelt es sich übrigens nicht um einen alten Greis 😃

Intel Core i7-2600 (4x3,4GHz)
8 GB DDR3 1333-SDRAM
Windows7 Ultimate

Wir wollen die folgende Einstellung des GC testen und erhoffen uns dadurch Verbesserungen:
GCSettings.LatencyMode = SustainedLowLatency;

Habt ihr damit schon Erfahrung?

P
1.090 Beiträge seit 2011
vor 10 Jahren

Wir wollen die folgende Einstellung des GC testen und erhoffen uns dadurch Verbesserungen:
GCSettings.LatencyMode = SustainedLowLatency;

Habt ihr damit schon Erfahrung?

Schau in GC performing full blocking Gen2 collections under SustainedLowLatency despite there is no LowMemory notification from OS.

Und selbst wenn das in der Dokumentation beschrieben Verhalten umgesetzt würde, ist es unwahrscheinlich, das es dir Helfen würde. Wenn ich dich richtig verstanden hab, ist ja zu dem Zeitpunkt ein Prozess am Arbeiten, dem nach ist es einfach sehr wahrscheinlich, das Speicher gebraucht wird. Und da muss dann der GC aktiv werden.

Grundlegend ist es dir doch egal ob der GC läuft oder nicht, solange der Code unter 200ms ausgeführt wird. Ich halte es deshalb für ein Performance Problem.

Du magst vielleicht in der Lage sein, fehlerfreien performanten Code ohne messen zu schreiben. Ich weiß ich bin es nicht. Deswegen schreibe ich Unit Test und messe bei Performance Problemen.

Und ohne weitere Informationen (z.B. relevante Code teile, wie Hoch ist der Speicherverbrauch usw.), kann ich an der Stelle nur raten.

@Abt
Die Schleife beim StringBuilder hab ich erwähnt und was da Beispiel angeht, bin ich ehrlich gesagt ein wenig verwundert das der StringBuilder noch so schnell ist wenn man ihn so Verwendet.

Mir geht es da auch nicht um Micro-Performance, sondern eher um Freitag Abend Code 😉.
Und ganz ehrlich, den haben wir alle schon mal geschrieben.

Mit freundlichen Grüßen
Björn

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Palin,

Grundlegend ist es dir doch egal ob der GC läuft oder nicht, solange der Code unter 200ms ausgeführt wird. Ich halte es deshalb für ein Performance Problem.

auch ich muss dir widersprechen. So egal ist es oft leider nicht. Wenn ein an sich performanter Algorithmus nach z.B. 10 Durchläufen soviel Speicher verbraucht hat, dass der GC anspringen muss und die verwaisten Objekte von 10 Durchläufen wegräumen muss, dann kann es durchaus sein, dass der GC den 11 Durchlauf so stört, dass dieser nicht innerhalb der vorgegebenen Zeit beendet werden kann. Lösungsmöglichkeiten dafür wurden allerdings schon ausführlich besprochen.

Natürlich hilft Performance des eigentlichen Algorithmuses, Pufferzeiten zu schaffen, aber wenn durch den GC eine Verzögerung von 200ms oder mehr verursacht wird, dann könnte selbst ein Algorithmus, der in quasi Nullzeit fertig wäre, die Aufgabe nicht in 200ms Gesamtzeit abschließen.

Also dafür sorgen, das Objekte eine möglichst kurze Lebensdauer haben,

Auch hier sehe ich es anders. Für die Klarheit von Programmen mag das zuträglich sein. Also im Normalfall sicher vorzuziehen.

Aber um den GC zu entlasten hilft eher das Gegenteil, nämlich einmal erstellte Objekt solange wie möglich zu nutzen, also dieselben Objekte wiederzuverwenden, statt immer wieder neue Objekt zu erzeugen und diese schnell wieder zu verwerfen.

Hier muss man allerdings noch unterscheiden zwischen Stack und Heap.

Parameter und lokale Variablen liegen auf den Stack und deren Speicher wird automatisch (und ohne Zutun des GCs) frei, wenn die Methode verlassen wird. Damit meine ich den Speicher für die Variablen selbst. Bei einer Referenzvariable würde also nur der Speicher für die Referenz sofort frei.

Wenn die Referenzvariable ein Objekt referenziert hat und dies die letzte Referenz war, dann kann der GC anschließend auch das zuvor referenzierte Objekt, welches auf dem Heap liegt, freigeben. Aber das macht er eben nicht sofort, sondern in Läufen, in denen er alle verweisen Objekte sammelt. Je mehr verweise Objekte, desto länger dauert das.

Wenn man also neue Objekte auf dem Heap erzeugt (typischerweise mit new) und diese möglichst kurz leben lässt, dann erzeugt man auf dem Heap viel Speicherverbrauch und damit für den GC viel Arbeit. Hier ist es also besser, Objekte einmal erzeugen und z.B. für den nächsten Durchlauf wiederzuverwenden.

Wenn man z.B. ein byte-Array braucht, um den die Pixeldaten von (immer gleich großen) Bilden, die analysiert werden soll, benötigt, dann ist es besser dieses Array nur einmal zu erzeugen, und dann für jedes Bild zu benutzen, als für jedes Bild immer ein neues Array zu erzeugen (und zu verwerfen).

Zur Klarheit: Ich bin kein Fan davon, echte Objekte ohne Not wiederzuverwenden. Im Gegenteil finde ich, dass im Normalfall die Identität der Objekt berücksichtigt und respektiert werden soll (zur Identität von Objekten siehe z.B. Kopie ohne ICloneable [oder warum man Objekte nicht kopieren sollte; Transaktionen auf Objekten]). Wenn in einem performance-unkritischen Programm nacheinander mehrere Rechnungen verarbeitet werden sollen, würde ich für jede Rechnung ein neues Objekt erzeugen. Aber wir reden hier über ein performance-kritisches Programm und da gelten eben mitunter andere Regeln.

herbivore

P
1.090 Beiträge seit 2011
vor 10 Jahren

Natürlich hilft Performance des eigentlichen Algorithmuses, Pufferzeiten zu schaffen, aber wenn durch den GC eine Verzögerung von 200ms oder mehr verursacht wird, dann könnte selbst ein Algorithmus, der in quasi Nullzeit fertig wäre, die Aufgabe nicht in 200ms Gesamtzeit abschließen.

Wenn der GC läuft und es länger als 200ms dauert, ist die Bedingung das es schneller als 200ms sein soll nicht erfüllt.

Daraus kann man natürlich herleiten das man alles macht damit der GC nicht läuft oder aber das man alles macht dass es schneller wird als 200ms. Wobei dann die Möglichkeit, dass der GC nicht läuft eine Teilmenge der Möglichen Lösungen ist.

Was man beim GC so machen kann haben wir ja schon betrachtet. Ich würde da jetzt gerne einen Schritt weiter gehen und mal schauen was man alles so machen kann damit es schneller als 200ms läuft.

Mit freundlichen Grüßen
Björn

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

16.806 Beiträge seit 2008
vor 10 Jahren

Ich würde ganz anders arbeiten und mich nicht am GC blockieren.
TPL Pipelinig ist in vielen Fällen eine saubere Lösung für ein vermeintliches "Verschlucken".

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Palin,

Ich würde da jetzt gerne einen Schritt weiter gehen und mal schauen was man alles so machen kann damit es schneller als 200ms läuft.

wir drehen uns leider im Kreis. Der Algorithmus als solcher läuft ja für sich genommen schneller als 200ms. Wenn man ihn noch weiter beschleunigen kann gut, aber das (alleine) wird das hier geschilderte Problem nicht lösen.

herbivore

W
872 Beiträge seit 2005
vor 10 Jahren

Wie schon früher gesagt, musst Du vor allem messen, wann Du zu langsam bist.
Ich kann dir mal ein Beispiel von mir aus der Praxis geben.
Ich rufe täglich im mehrstelligen Millionen-Bereich ein delegate auf und messe, wie lange der Aufruf im Durchschnitt und wie lange der langsamste Aufruf dauert.
Ich habe oft einen Durchschnitt von 2-3 Ticks bei einer Kapazität von 2 Mio. Ticks/Sekunde, trotzdem liegt der Maximalwert bei ca. 1 Sekunde. Fuer den langsamsten Wert speicher ich immer die Parameter beim Aufruf des delegates, um zu schauen, was genau durchlaufen wurde und da sehe ich, dass es jedesmal ein anderer Ast des Programmes ist.
Wahrscheinlich musst Du mit 99.9 % innerhalb von 200 ms zufrieden sein und dann für den Rest der Fälle eine Fehlerbehandlung einplanen.

P
1.090 Beiträge seit 2011
vor 10 Jahren

Ich Probiers mal kurz und Pregnant.

@ mutitasker
Mache bitte mal Angaben zu folgenden Punkten:
Speicherverbrauch der Anwendung
Dauer von GC.Collect nach der Ausführung des Codes

MFG
Björn

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern