Question

Je suis enchaînant 15 opérations asynchrones dans les ports et les récepteurs. Cela m'a laissé très préoccupé par le temps de messagerie interthread, plus précisément le temps qu'il faut entre une donnée de publication de la tâche à un port et une nouvelle tâche commence le traitement que les mêmes données sur un thread différent. En supposant meilleure situation de cas où chaque thread est inactif au début, j'ai généré un test qui utilise la classe chronomètre pour mesurer le temps de deux répartiteurs différents fonctionnant chacun à la priorité la plus élevée avec un seul fil.

Ce qui m'a m'a surpris, ma plate-forme de développement est un Q6600 Quad Core Ordinateur 2,4 Ghz en cours d'exécution x64 Windows 7, et le temps de changement de contexte moyen de mon test était 5,66 microsecondes avec un écart-type de 5.738 microsecondes, et un maximum de près 1,58 millisecondes (un facteur de 282!). La fréquence Chronomètre est 427,7 nano secondes, donc je suis toujours bien sur le bruit du capteur.

Ce que je voudrais faire est de réduire le temps de messagerie interthread autant que possible, et tout aussi important, de réduire l'écart-type du changement de contexte. Je me rends compte Windows est pas en temps réel OS, et il n'y a pas de garanties, mais le planificateur Windows est un programme basé sur la priorité du tournoi à la ronde équitable, et les deux fils dans ce test sont tous deux à la plus haute priorité (les seuls sujets qui devraient être que élevé), donc il ne devrait pas y avoir de changements de contexte sur les fils (évidents par les 1,58 ms plus grand temps ... Je crois que les fenêtres quanta est 15,65 ms?) la seule chose que je peux penser à une variation dans le calendrier des appels OS les mécanismes de verrouillage utilisés par le CCR pour transmettre des messages entre les threads.

S'il vous plaît laissez-moi savoir si quelqu'un d'autre là-bas a mesuré le temps de messagerie interthread, et a des suggestions sur la façon de l'améliorer.

Voici le code source de mes 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();
        }
    }
}
Était-ce utile?

La solution

Windows n'est pas un système d'exploitation en temps réel. Mais tu le savais déjà. Qu'est-ce que vous tuer est le temps de changement de contexte, pas nécessairement temps de message. Vous ne spécifiez pas vraiment comment fonctionne votre communication inter-processus de. Si votre vraiment juste courir plusieurs threads, vous trouverez des gains par l'absence de message Windows comme protocole de communication, essayez plutôt rouler votre propre IPC en utilisant des files d'attente de messages d'application hébergée à la place.

La meilleure moyenne que vous pouvez espérer est 1ms avec une version de Windows lors du changement se produit contexte. Votre probablement voir les temps de 1ms lorsque votre application doit céder au noyau. Ceci est par la conception pour les applications Ring-1 (espace utilisateur). S'il est absolument essentiel que vous obtenez ci-dessous 1ms vous aurez besoin de passer une partie de votre application dans Ring-0, ce qui signifie l'écriture d'un pilote de périphérique.

Pilotes de périphériques ne souffrent pas les mêmes temps contexte que les applications de commutation utilisateurs font, et avoir accès à nano secondes minuteries de résolution et le sommeil appelle ainsi. Si vous avez besoin de le faire, le DDK (Device Driver Kit de développement) est disponible gratuitement auprès de Microsoft, mais je vous recommande vivement d'investir dans un kit de développement 3ème partie. Ils ont généralement de très bons échantillons et beaucoup de sorciers pour mettre les choses à droite qui vous prendre des mois de lecture de documents DDK à découvrir. Vous aurez également besoin d'obtenir quelque chose comme SoftIce parce que le débogueur normale Visual Studio ne va pas vous aider à déboguer des pilotes de périphériques.

Autres conseils

Est-ce que les 15 opérations asynchrones Vous pour être asynchrone? dire que vous forcé de fonctionner de cette façon par une limitation de certaines bibliothèques, ou avez-vous le choix de faire des appels synchrones?

Si vous avez le choix, vous devez structurer votre application afin que l'utilisation de asynchronisme est contrôlé par les paramètres de configuration. La différence entre les opérations asynchrones qui renvoient à un fil différent par rapport à des opérations synchrones que le retour sur le même fil doit être transparent dans le code. De cette façon, vous pouvez régler sans changer la structure du code.

L'expression « honteusement parallèle » décrit un algorithme dans lequel la majorité du travail se fait est évidemment indépendant et ne peut donc se faire dans l'ordre, le rendant facile à paralléliser.

Mais vous « enchaînant 15 opérations asynchrones dans les ports et les récepteurs ». Cela pourrait être décrit comme « séquentiel honteusement ». En d'autres termes, pourrait être logiquement écrit le même programme sur un seul thread. Mais alors vous perdriez tout parallélisme pour le travail survenant lié au processeur entre les opérations asynchrones (en supposant qu'il ya une signification).

Si vous écrivez un test simple pour découper tout travail de CPU lié important et juste mesure le temps de commutation contexte, puis devinez quoi, vous allez être la mesure de la variation dans le temps de commutation de contexte, comme vous avez découvert .

La seule raison pour exécuter plusieurs threads est parce que vous avez des quantités importantes de travail pour les CPU à faire, et si vous souhaitez le partager entre plusieurs processeurs. Si les morceaux individuels de calcul sont assez courte durée, alors le changement de contexte sera une surcharge importante sur any OS. En brisant votre calcul vers le bas en 15 étapes, chacune très court, vous demandez essentiellement le système d'exploitation pour faire beaucoup de changements de contexte inutile.

ThreadPriority.Highest ne signifie pas que le planificateur de fil lui-même a une priorité plus élevée. L'API Win32 a un niveau plus granulaire de priorité de fil ( clicky) avec plusieurs niveaux au-dessus le plus élevé (IIRC le plus élevé est généralement le plus haut le code de priorité non-admin peut être exécuté à, les administrateurs peuvent planifier une priorité plus élevée que possible tout code de mode pilotes de matériel / kernel) donc il n'y a aucune garantie qu'ils pas préemptées.

Même si un thread est en cours d'exécution avec les plus hautes fenêtres prioritaires peuvent promouvoir d'autres sujets au-dessus de leur priorité de base si elles détiennent des verrous de ressources que les fils plus prioritaires exigent ce qui est une autre possibilité pourquoi vous souffrez peut-être des changements de contexte.

Même alors, comme vous le dites, Windows n'est pas un système d'exploitation en temps réel et il n'est pas garanti d'honorer les priorités de fil de toute façon.

Pour attaquer ce problème d'une manière différente, vous avez besoin d'avoir tant d'opérations asynchrones découplés? Il peut être utile de penser à: partitionner le travail verticalement (de façon asynchrone traiter des morceaux de numCores de fin de données à la fin) à la place de partitionnement horizontal du travail (comme maintenant, avec chaque bloc des données traitées en 15 étapes découplés); couplage synchrone certains de vos 15 étapes pour réduire le total à un plus petit nombre.

Les frais généraux de la communication inter-thread sera toujours non trivial. Si seulement font un petit morceau de travail, les changements de contexte vous mordre certains de vos 15 opérations.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top