Welche Optimierungen können für weichen Echtzeitcode in C # vorgenommen werden?

8

Ich schreibe eine weiche Echtzeitanwendung in C #. Bestimmte Aufgaben, wie das Beantworten von Hardwareanfragen, die von einem Netzwerk eingehen, müssen innerhalb einer bestimmten Anzahl von Millisekunden abgeschlossen sein. Dies ist jedoch nicht zu 100% geschäftskritisch (dh wir können es tolerieren, dass es die meiste Zeit pünktlich ist, und 1% ist unerwünscht, aber kein Fehler), daher der "weiche" Teil.

Jetzt ist mir klar, dass C # eine verwaltete Sprache ist und verwaltete Sprachen nicht besonders für Echtzeitanwendungen geeignet sind. Die Geschwindigkeit, mit der wir Dinge in C # erledigen können, sowie die Sprachfunktionen wie Reflexion und Speicherverwaltung erleichtern die Erstellung dieser Anwendung jedoch erheblich.

Gibt es Optimierungen oder Entwurfsstrategien, mit denen der Overhead reduziert und der Determinismus erhöht werden kann? Idealerweise hätte ich folgende Ziele

  • Verzögern Sie die Speicherbereinigung, bis sie "sicher" ist.
  • Lassen Sie den Garbage Collector arbeiten, ohne die Echtzeitprozesse zu beeinträchtigen
  • Thread- / Prozessprioritäten für verschiedene Aufgaben

Gibt es Möglichkeiten, dies in C # zu tun, und gibt es andere Dinge, auf die Sie in Bezug auf Echtzeit bei der Verwendung von C # achten müssen?

Das Plattformziel für die Anwendung ist .NET 4.0-Clientprofil unter Windows 7 64-Bit mit. Ich habe es derzeit auf Client-Profil eingestellt, aber dies war nur die Standardoption und wurde aus einem bestimmten Grund nicht ausgewählt.

9a3eedi
quelle
2
Dieser Artikel könnte Ihnen helfen. Es richtet sich an Entwickler von Videospielen, ist aber auch eine weiche Echtzeitanwendung. Der Artikel ist jedoch auch etwas alt, und obwohl ich bezweifle, dass sich die Grundprinzipien für die Funktionsweise von GC stark geändert haben, sollten Sie überprüfen, ob er noch auf dem neuesten Stand ist.
Doval
Sie erwähnen C #, aber nicht Ihre Zielplattform oder Laufzeit (z. B. .NET auf einem PC / Unity + Mono / Mono usw.) - können Sie uns ein paar weitere Details geben?
J Trana
@JTrana Ich habe mit mehr Details bearbeitet
9a3eedi
1) Haben Sie tatsächlich Probleme bemerkt? Dank des Hintergrund-GC sollten Ihre Threads für bestimmte Teile des GC nur kurz anhalten, aber viele andere Teile können ohne Störung parallel laufen. 2) Über wie viele Millisekunden sprechen wir hier?
CodesInChaos
2
@Doval Der GC des von XNA auf der Xbox verwendeten kompakten Frameworks ist viel schlechter als der im normalen Framework verwendete GC. Es gab signifikante Änderungen am GC in .net 4.0 im Zusammenhang mit der Hintergrundsammlung, die der Latenz zugute kommen sollten. Mit der Hintergrundsammlung kann die teure Gen2-Sammlung im Hintergrund ausgeführt werden, während Ihre Anwendung weiterarbeitet. Es können sogar Gen0 / Gen1-Sammlungen ausgeführt werden, während die Gen2-Sammlungen ausgeführt werden.
CodesInChaos

Antworten:

7

Die Optimierung, die Ihre ersten beiden Aufzählungszeichen erfüllt, wird als Objektpool bezeichnet . Es funktioniert von

  1. Erstellen eines Objektpools beim Starten des Programms,
  2. Verweise auf diese Objekte in einer Liste beibehalten, damit kein Müll gesammelt wird,
  3. Übergeben von Objekten aus dem Pool an Ihr Programm nach Bedarf und
  4. Zurückgeben von Objekten an den Pool, wenn Sie sie nicht mehr verwenden.

Sie können ein Beispiel Klasse finden , die ein Objekt Pool mit einem implementiert ConcurrentBag hier .

Die Thread- / Prozesspriorität kann einfach zur Laufzeit festgelegt werden. Die Thread-Klasse verfügt über Methoden und Eigenschaften, mit denen Sie Priorität und Prozessoraffinität festlegen können. Die Prozessklasse enthält ähnliche Funktionen.

Robert Harvey
quelle
3
Dies scheint etwas zu sein, das getan werden sollte, nachdem die Anwendung abgeschlossen ist und funktioniert, wie in "Später optimieren". Trotzdem ist es für mich als Methode zur Steigerung des Determinismus sinnvoll.
9a3eedi
1
Würden meine Vorschläge "früher optimieren" sein?
Robert Harvey
2
Der Garbage Collector verfügt nur über wenige Konfigurationsoptionen. Es wird allgemein angenommen, dass sein Verhalten für die überwiegende Mehrheit der Programme optimal ist. Beachten Sie, dass weder das Windows-Betriebssystem noch .NET Framework für die Verwendung in harten Echtzeitumgebungen vorgesehen sind. Ja, ich weiß, dass Sie in Echtzeit leise gesagt haben, aber trotzdem.
Robert Harvey
2
@ 9a3eedi: In Bezug darauf, ob der Objektpool nach Abschluss der Anwendungsentwicklung angeschraubt werden kann, kann ich folgenden Rat geben. (1) Sie müssen eine Objektfactory (die gleichzeitig als Objektpool fungiert) in alle Objekte einfügen, die Sie bündeln möchten. (2) Ersetzen Sie alle Konstruktormethoden durch Factory-Methoden. (3) Jedes Objekt verfügt über eine Recyclingmethode kehren Sie zur Poolfabrik zurück (4) implementieren Sie Ihre Objekte so, dass sie recycelbar sind: Sie werden gelöscht und dann mit neuen Informationen neu geschrieben. Sie können dies berücksichtigen, wenn Sie die API-Schnittstelle im Voraus entwerfen müssen.
Rwong
3
Nach meiner Erfahrung eignen sich Objektpools zum Recycling großer Arrays (z. B. Puffer). Bei kleinen Objekten ist die Laufzeit ohne manuelles Pooling in der Regel gut.
CodesInChaos
3

Verzögern Sie die Speicherbereinigung, bis sie "sicher" ist.

Sie können dies tun, indem Sie den GC-Latenzmodus auf einstellen LowLatency. Weitere Informationen finden Sie in dieser Antwort auf SO .

svick
quelle
Ausgezeichnet. Ich habe so etwas gesucht. Vielen Dank. Ich wünschte, ich könnte zwei richtige Antworten markieren
9a3eedi
2

Gehen Sie genauso vor wie bei der Spieleentwicklung in einer verwalteten Umgebung und versuchen Sie, die Objekterstellung / den Tod auf ein absolutes Minimum zu reduzieren.

Versuchen Sie beispielsweise, alle Objekte zu erstellen, die zu Beginn wahrscheinlich benötigt werden, und bündeln Sie sie zwanghaft. Gehen Sie nach Möglichkeit an ref vorbei und vermeiden Sie Operationen, die kurzfristige Zwischenobjekte erstellen

lzcd
quelle
Wird das Ändern einiger Klassen in (unveränderliche) Strukturen dazu beitragen, das Erstellen kurzfristiger Zwischenobjekte zu vermeiden?
9a3eedi
Die Wahl zwischen Strukturen und Klassen hat im Allgemeinen keinen großen Einfluss auf die Lebensdauer eines "Objekts" direkt (außer dass Strukturen mit einer eingebauten "Singleton" -Logik vom Wertetyp geliefert werden). Unveränderlich zu sein mag helfen, aber es hängt wirklich von Fall zu Fall ab.
lzcd
1
@ 9a3eedi: Die Verwendung unveränderlicher Objekte bedeutet, dass Sie immer dann, wenn Sie etwas "mutieren" müssen, ein neues Objekt mit geänderten Werten erstellen müssen (und wahrscheinlich das alte Objekt im Austausch gegen das neue wegwerfen müssen). Das ist genau das Gegenteil von dem, was Sie wollen, da dadurch viel Speicher für den GC aufgeräumt werden muss.
Doc Brown
@DocBrown +1. GCs sind jedoch auf kurzlebige Objekte abgestimmt. Sie zahlen nicht für Objekte, die tot sind, wenn eine Sammlung stattfindet (obwohl das Zuweisen wie verrückt die Häufigkeit von Sammlungen erhöht). Sie liegen nicht falsch, aber es lohnt sich zu überprüfen, ob es überhaupt ein Problem gibt, da das Pooling eine manuelle Speicherverwaltung darstellt und den halben Zweck des verwalteten Codes
zunichte macht
@DocBrown Außerdem werden Objekte, von denen der Compiler / die Laufzeit nachweisen kann, dass sie nicht ihrem Gültigkeitsbereich entgehen, eher auf dem Stapel als auf dem Heap zugewiesen. In einigen Fällen haben temporäre Objekte keinerlei Auswirkungen auf die Speicherbereinigung.
Doval
0

Beachten Sie, dass die Zielumgebung (Windows) ein vorbeugendes Multitasking-Betriebssystem ist. Mit anderen Worten, Ihr weicher Echtzeitprozess wird zwangsläufig irgendwann vorweggenommen, was neben der .NET-Speicherbereinigung auch andere Latenzen zur Folge hat. Sie können dies verringern, indem Sie den Zeitscheibenwert (Quantenwert) reduzieren (Standard ist etwa 16 ms) und die Priorität Ihres Prozesses erhöhen (es gibt jedoch praktische Grenzen). Es werden jedoch / müssen immer noch Vorkehrungen getroffen werden, da andere wichtige Prozesse noch erforderlich sind Sachen machen. All dies hat nichts mit Programmiersprache zu tun; Es ist grundlegend für das O / S.

Abgesehen von GC-Problemen kann Ihre Anwendung, die auf einem sofort einsatzbereiten Windows-Computer ausgeführt wird, wahrscheinlich die meiste Zeit Latenzen von einigen Millisekunden liefern. Es kann sicherlich nicht 100% der Zeit garantiert werden, oder irgendetwas in der Nähe. Selbst bei der Abstimmung (kurzes Quantum, hohe Priorität) treten gelegentlich hohe Latenzen auf. Es ist die Natur des Tieres.

Fazit: Wenn Sie garantierte Antwortzeiten benötigen, insbesondere in der Größenordnung von wenigen Millisekunden, ist Windows wahrscheinlich nicht die beste Wahl für die Zustellung.

Zenilogix
quelle
Genau. Ich bin bereit, hier und da einige Millisekunden Verzögerungen zu akzeptieren, daher der Teil "weiche Echtzeit", aber im Idealfall möchte ich so viel wie möglich minimieren. Vielleicht kann ich die Anwendung später auf Linux portieren (mit Mono) und vielleicht kann ich diese Dinge dann noch
optimieren