Frage

Ich bin Verkettungs zusammen 15 Asynchron-Operationen durch Häfen und Empfänger. Dies hat mich mit dem interthread Messaging Zeit sehr besorgt, und zwar die Zeit, die zwischen einer Aufgabe Buchungsdaten an einen Port führt, und eine neue Aufgabe beginnt mit der Verarbeitung der gleichen Daten auf einem anderen Thread. beste Fall Situation Unter der Annahme, wo jeder Thread beim Start im Leerlauf ist, ich habe einen Test erzeugt, die die Stoppuhr-Klasse verwendet, die Zeit von zwei verschiedenen Disponenten jeden Betrieb bei höchster Priorität mit einem einzigen Thread zu messen.

Was ich mich überrascht gefunden, meine Entwicklung rig ist ein Q6600 Quad-Core 2,4 Ghz Computer Windows 7 x64 ausgeführt wird, und die durchschnittliche Kontextwechselzeit von meinem Test war 5,66 Mikrosekunden mit einer Standardabweichung von 5,738 Mikrosekunden, und ein Maximum von fast 1,58 Millisekunden (Faktor 282!). Die Stoppuhr Die Frequenz ist 427,7 Nanosekunden, so dass ich aus Sensorrauschen noch gut bin.

Was würde Ich mag zu tun ist, um die interthread Messaging-Zeit so weit wie möglich zu reduzieren, und ebenso wichtig ist, die Standardabweichung des Kontextwechsels reduzieren. Ich weiß, Windows keine Echtzeit OS ist, und es gibt keine Garantie, aber die Fenster Scheduler ist ein fairer Round-Robin-Priorität basierte Zeitplan und die beiden Themen in diesem Test sind beide mit der höchsten Priorität (die einzigen Threads, dass sein sollte hoch), so dass es keine Kontextwechsel auf dem Gewinde (offensichtlich durch die 1,58 ms größte Zeit sein sollte ... ich glaube, Fenster Quanta 15,65 ms ist?) Das einzige, was ich ist Variation in der zeitlichen Abfolge der OS Anrufe denken bis die Verriegelung durch die CCR verwendeten Mechanismen Nachrichten zwischen Threads zu übergeben.

Bitte lassen Sie mich wissen, ob jemand aus hat es interthread Messaging-Zeit gemessen, und hat Vorschläge, wie es zu verbessern.

Hier ist der Quellcode von meinen Tests:

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Microsoft.Ccr.Core;

using System.Diagnostics;

namespace Test.CCR.TestConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting Timer");
            var sw = new Stopwatch();
            sw.Start();

            var dispatcher = new Dispatcher(1, ThreadPriority.Highest, true, "My Thread Pool");
            var dispQueue = new DispatcherQueue("Disp Queue", dispatcher);

            var sDispatcher = new Dispatcher(1, ThreadPriority.Highest, true, "Second Dispatcher");
            var sDispQueue = new DispatcherQueue("Second Queue", sDispatcher);

            var legAPort = new Port<EmptyValue>();
            var legBPort = new Port<TimeSpan>();

            var distances = new List<double>();

            long totalTicks = 0;

            while (sw.Elapsed.TotalMilliseconds < 5000) ;

            int runCnt = 100000;
            int offset = 1000;

            Arbiter.Activate(dispQueue, Arbiter.Receive(true, legAPort, i =>
                                                                            {
                                                                                TimeSpan sTime = sw.Elapsed;
                                                                                legBPort.Post(sTime);
                                                                            }));
            Arbiter.Activate(sDispQueue, Arbiter.Receive(true, legBPort, i =>
                                                                             {
                                                                                 TimeSpan eTime = sw.Elapsed;
                                                                                 TimeSpan dt = eTime.Subtract(i);
                                                                                 //if (distances.Count == 0 || Math.Abs(distances[distances.Count - 1] - dt.TotalMilliseconds) / distances[distances.Count - 1] > 0.1)
                                                                                 distances.Add(dt.TotalMilliseconds);

                                                                                 if(distances.Count > offset)
                                                                                 Interlocked.Add(ref totalTicks,
                                                                                                 dt.Ticks);
                                                                                 if(distances.Count < runCnt)
                                                                                     legAPort.Post(EmptyValue.SharedInstance);
                                                                             }));


            //Thread.Sleep(100);
            legAPort.Post(EmptyValue.SharedInstance);

            Thread.Sleep(500);

            while (distances.Count < runCnt)
                Thread.Sleep(25);

            TimeSpan exTime = TimeSpan.FromTicks(totalTicks);
            double exMS = exTime.TotalMilliseconds / (runCnt - offset);

            Console.WriteLine("Exchange Time: {0} Stopwatch Resolution: {1}", exMS, Stopwatch.Frequency);

            using(var stw = new StreamWriter("test.csv"))
            {
                for(int ix=0; ix < distances.Count; ix++)
                {
                    stw.WriteLine("{0},{1}", ix, distances[ix]);
                }
                stw.Flush();
            }

            Console.ReadKey();
        }
    }
}
War es hilfreich?

Lösung

Windows ist kein Echtzeitbetriebssystem. Aber Sie wussten bereits, dass. Was Sie zu töten ist die Kontextschaltzeiten, die nicht unbedingt Nachricht Zeiten. Sie haben wirklich nicht fest, wie der inter Prozesse Kommunikation funktioniert. Wenn Sie wirklich nur mehrere Threads laufen, werden Sie einige Gewinne durch nicht mit dem Windows-Nachricht als ein Kommunikationsprotokoll zu finden, stattdessen versuchen, Ihren eigenen IPC rollt anstatt mit Anwendung gehostet Nachrichtenwarteschlangen.

Die beste durchschnittlich man hoffen kann, ist 1ms mit jeder Version von Windows, wenn Kontext auftritt, schaltet. Sie wahrscheinlich sehen die 1ms Zeiten, in denen Ihre Anwendung an den Kernel zu erhalten. Das ist von Entwurf für Ring-1-Anwendungen (User-Space). Wenn es absolut entscheidend ist, dass Sie unter 1ms erhalten Sie benötigen einige Ihrer Anwendung in Ring-0 zu schalten, welche Mittel einen Gerätetreiber zu schreiben.

Gerätetreiber leiden nicht die gleichen Kontext-Umschaltzeiten, dass Benutzer Anwendungen zu tun, und haben Zugriff auf Nanosekunden Auflösung Timer und Schlaf Anrufe als auch. Wenn Sie tun, dies zu tun, sind die DDK (Device Driver Development Kit) von Microsoft frei verfügbar, aber ich würde empfehlen, Sie in einem 3rd-Party-Development-Kit zu investieren. Sie haben in der Regel wirklich gute Proben und viele Assistenten Set Dinge richtig, dass Sie Monate des Lesens DDK Dokumente dauern würde, zu entdecken. Sie wollen auch so etwas wie SoftIce zu bekommen, weil das normale Visual Studio-Debugger nicht helfen wird Sie Gerätetreiber debuggen.

Andere Tipps

Haben die 15 asynchrone Operationen wurde asynchron sein? das heißt sind Sie auf diese Weise durch eine Einschränkung einiger Bibliothek zu arbeiten gezwungen oder haben Sie die Wahl synchrone Anrufe zu tätigen haben?

Wenn Sie die Wahl haben, müssen Sie Ihre Anwendung so strukturieren, dass die Verwendung von Asynchronität durch Konfigurationsparameter gesteuert wird. Die Differenz zwischen asynchronen Operationen, der Rückkehr auf einem anderen Thread vs. synchrone Operationen, die Rendite auf dem gleichen Thread soll im Code transparent sein. Auf diese Weise können Sie es stimmen, ohne dass die Code-Struktur zu ändern.

Der Ausdruck „peinliche parallel“ beschreibt ein Algorithmus, in dem der Großteil der Arbeit getan offensichtlich unabhängig ist und so kann in beliebiger Reihenfolge durchgeführt wird, ist es leicht zu parallelisieren zu machen.

Aber Sie sind „Verkettungs zusammen 15 Asynchron-Operationen durch Häfen und Empfänger“. Dies könnte als „peinliche sequential“ beschrieben. Mit anderen Worten könnte das gleiche Programm logisch auf einem einzigen Thread geschrieben werden. Aber dann würden Sie jede Parallelität für die CPU-gebundene Arbeit auftretenden zwischen den Asynchron-Operationen verlieren (vorausgesetzt, es ist ein von Bedeutung).

Wenn Sie einen einfachen Test schreiben eine signifikante CPU-gebundene Arbeit auszuschneiden und messen nur das Kontextschaltzeit, dann erraten, was Sie vorhaben, die Variation im Rahmen Schaltzeit zu messen, wie Sie entdeckt haben .

Der einzige Grund, mehrere Threads für die Ausführung liegt daran, dass Sie erhebliche Mengen an Arbeit haben für CPUs zu tun, und so Sie mögen, dass es teilen zwischen mehreren CPUs aus. Wenn die einzelnen Stücke der Berechnung sind kurzlebig genug, dann wird Kontextwechsel ein signifikanter Overhead auf werden jede OS. Durch das Brechen Ihrer Berechnung nach unten in 15 Stufen, die jeweils sehr kurz, fragen Sie im Wesentlichen das Betriebssystem eine Menge unnötiger Kontextwechsel zu tun.

ThreadPriority.Highest bedeutet nicht nur den Thread-Scheduler selbst eine höhere Priorität hat. Die Win32-API hat einen detaillierteren Ebene der Thread-Priorität ( clicky ) mit mehreren Ebenen über höchste (IIRC höchste ist in der Regel die höchste Priorität nicht-Admin-Code kann ausgeführt werden, Administratoren wie können höhere Priorität planen kann eine beliebige Hardware-Treiber / Kernel-Modus-Code), so gibt es keine Garantie, sie werden nicht vorbelegt werden.

Auch wenn ein Thread mit der höchsten Priorität Fenster laufen andere Threads über ihre Basispriorität fördern können, wenn sie Ressourcensperren halten, dass Threads mit höherer Priorität erfordern, was eine andere Möglichkeit ist, warum können Sie Leiden Kontextwechsel sein.

Auch dann, wie Sie sagen, Windows ist keine Echtzeit-Betriebssystem und es ist ohnehin nicht zu Ehre Thread-Prioritäten gewährleistet.

Um dieses Problem eine andere Art und Weise angreifen, müssen Sie so viele entkoppelten asynchrone Operationen haben? Es kann nützlich sein, um darüber nachzudenken: vertikal Partitionieren die Arbeit statt horizontal Partitionieren die Arbeit (wie jetzt mit jedem Datenblock verarbeitet in 15 entkoppelten Stufen) (asynchron numCores Datenblöcke Ende zu Ende-Verfahren); einige Ihrer 15 Stufen synchron Kopplung des Gesamt auf eine kleinere Zahl zu reduzieren.

der Overhead von inter-thread-Kommunikation wird immer nicht-trivial sein. Wenn einige Ihrer 15 Operationen nur ein kleines Stück Arbeit leisten, werden die Kontextschalter Sie beißen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top