Pregunta

Estoy encadenar 15 operaciones asíncronas a través de puertos y receptores. Esto me ha dejado muy preocupados con el tiempo de la mensajería interthread, específicamente el tiempo que se tarda entre unos datos de contabilización tarea a un puerto, y una nueva tarea comienza el procesamiento de los mismos datos en un subproceso diferente. Suponiendo mejor situación caso en el que cada hilo está inactivo al inicio, he generado una prueba que utiliza la clase cronómetro para medir el tiempo a partir de dos diferentes despachadores cada operando a máxima prioridad con un solo hilo.

Lo que me encontré sorprendido, mi equipo de desarrollo es un Q6600 Quad Core equipo 2.4 Ghz con Windows 7 x64, y el tiempo medio de cambio de contexto de mi prueba fue de 5,66 microsegundos con una desviación estándar de 5.738 microsegundos, y un máximo de casi 1,58 milisegundos (un factor de 282!). La frecuencia del cronómetro es 427,7 nano segundos, por lo que estoy todavía muy por fuera del ruido del sensor.

Lo que me gustaría hacer es reducir el tiempo de mensajería interthread tanto como sea posible, e igualmente importante, reducir la desviación estándar del cambio de contexto. Me doy cuenta de Windows no es un sistema operativo de tiempo real, y no hay garantías, pero el programador de Windows es un programa basado en la prioridad de round robin justo, y los dos hilos en este ensayo son tanto en la prioridad más alta (los únicos temas que deberían ser que alta), por lo que no debería haber ninguna cambios de contexto en las roscas (evidente por los 1,58 ms tiempo más grande ... creo ventanas quanta es 15,65 ms?) la única cosa que puedo pensar es la variación en el tiempo de las llamadas del sistema operativo a los mecanismos de bloqueo utilizados por el CCR para pasar mensajes entre las roscas.

Por favor, hágamelo saber si alguien más por ahí ha medido el tiempo de mensajería interthread, y tiene alguna sugerencia sobre cómo mejorarlo.

Aquí está el código fuente de mis pruebas:

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();
        }
    }
}
¿Fue útil?

Solución

Windows no es un sistema operativo de tiempo real. Pero tu ya lo sabias. Lo que está matando es los tiempos de cambio de contexto, no necesariamente la hora del mensaje. Que en realidad no especifica cómo funciona la comunicación inter-procesos. Si su realmente sólo la ejecución de múltiples hilos, encontrará algunas ganancias al no utilizar mensajes de Windows como un protocolo de comunicación, en lugar de tratar rodar su propia IPC utilizando aplicación alojada colas de mensajes en su lugar.

El mejor promedio que se puede esperar es de 1 ms con cualquier versión de Windows cuando se produce cambios de contexto. Su probablemente viendo los tiempos de 1 ms cuando su aplicación tiene que ceder al núcleo. Esto es por diseño para Ring-1 aplicaciones (de espacio de usuario). Si es absolutamente crítico que se obtiene por debajo de 1ms tendrá que cambiar algunos de su aplicación en el anillo-0, lo que significa escribir un controlador de dispositivo.

Los controladores de dispositivo no sufrir las mismas horas, cambio de contexto que las aplicaciones de usuario hacer y tener acceso a los contadores de tiempo de nanosegundos de resolución y el sueño llama así. Si necesita hacer esto, el DDK (Kit de desarrollo de controladores de dispositivos) está libremente disponible de Microsoft, pero encarecidamente recomendaría invertir en un kit de desarrollo de tercera parte. Por lo general, tienen muy buenas muestras y un montón de asistentes para poner las cosas encima de la derecha que tomaría meses de lectura de documentos DDK para descubrir. Usted también querrá obtener algo así como SoftIce debido a que el normal de Visual Studio depurador no va a ayudarle a depurar los controladores de dispositivos.

Otros consejos

¿Las 15 operaciones asincrónicas Tienes para ser asíncrona? es decir, que está obligado a operar de esta manera por una limitación de alguna biblioteca, o tiene la opción de hacer llamadas síncronas?

Si usted tiene la opción, es necesario que la estructura de su aplicación, de modo que el uso de la asincronía es controlado por los parámetros de configuración. La diferencia entre las operaciones asincrónicas que devuelven en un subproceso diferente vs. operaciones síncronas que el retorno en el mismo subproceso debe ser transparente en el código. De esa manera se puede sintonizar sin cambiar la estructura del código.

La frase "vergonzosamente paralelas", describe un algoritmo en el que está haciendo la mayor parte del trabajo es, obviamente, independiente y así se puede hacer en cualquier orden, por lo que es fácil de paralelizar.

Sin embargo, usted está "encadenar 15 operaciones asíncronas a través de puertos y receptores". Esto podría ser descrito como "secuencial vergonzosamente". En otras palabras, el mismo programa podría ser lógicamente escrito en un solo hilo. Pero entonces usted perdería cualquier paralelismo para la occuring trabajo vinculado a la CPU entre las operaciones asíncronas (suponiendo que haya alguno de importancia).

Si se escribe una prueba sencilla para cortar cualquier trabajo significativo vinculado a la CPU y simplemente medir el tiempo de conmutación de contexto, entonces supongo que lo que va a ser la medición de la variación en el tiempo de conmutación de contexto, como usted ha descubierto .

La única razón para la ejecución de múltiples hilos es porque tiene una cantidad significativa de trabajo para la CPU que se pueden hacer, por lo que desea compartirlo entre varias CPUs. Si los trozos individuales de cómputo son de corta duración suficiente, entonces el cambio de contexto será una sobrecarga significativa en cualquier OS. Al romper el cómputo hacia abajo en 15 etapas, cada uno muy corto, que está pidiendo esencialmente el sistema operativo para hacer un montón de cambios de contexto innecesaria.

ThreadPriority.Highest no significa sólo el programador de subprocesos en sí tiene una prioridad más alta. La API de Win32 tiene un nivel más granular de prioridad de subprocesos ( luxación) con varios niveles por encima de más alta (IIRC más alto es normalmente el código de prioridad más alta que no es administrador puede funcionar a, los administradores pueden programar una prioridad más alta al igual que cualquier controladores de hardware / kernel código modo) por lo que no hay garantía de que se no ser adelantado a.

Incluso si un hilo se ejecuta con las ventanas de mayor prioridad puede promover otros hilos por encima de su prioridad de base si se mantienen los bloqueos de recursos que requieren hilos de mayor prioridad que es otra razón por la posibilidad que puede estar sufriendo cambios de contexto.

Incluso entonces, como usted dice, Windows no es un sistema operativo de tiempo real y no se garantiza en honor prioridades de los hilos de todos modos.

Para atacar este problema de una manera diferente, no es necesario tener tantas operaciones asincrónicas desacoplado? Puede ser útil pensar acerca de: dividir verticalmente la obra (de forma asíncrona procesar numCores trozos de extremo a extremo de datos) en lugar de dividir horizontalmente la obra (como ahora, con cada trozo de datos tratados en 15 etapas desacopladas); sincrónicamente acoplamiento de algunos de sus 15 etapas para reducir el total a un número más pequeño.

La sobrecarga de la comunicación inter-hilo siempre será no trivial. Si algunos de sus 15 operaciones están haciendo sólo una pequeña parte de trabajo, los interruptores de contexto van a morder.?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top