Compartir a través de


Este artículo proviene de un motor de traducción automática.

Programación basada en tareas

Programación multiproceso escalable con tareas

Ron Fosner

Equipos están en evolución de procesadores más rápidos y más rápidos y hacia cada vez más núcleos. Esto significa que el aumento de capacidad de procesamiento latente está disponible al costo relativamente bajo. Pero también significa aprovechar las ventajas de estos sistemas de programación que potencia de procesamiento latente es más complicada. Para utilizar todos los procesadores múltiples, debe profundizar en el mundo de procesamiento en paralelo.

Hay muchas maneras distintas de distribuir el trabajo en varios núcleos. En el número de octubre de de MSDN Magazine (msdn.microsoft.com/magazine/gg232758 ), he presentado algunos conceptos básicos de programación con subprocesamiento múltiple y mostramos cómo agregar el subproceso de ejecución en el código con los grupos de OpenMP y subproceso. También demostré cómo usar herramientas de Visual Studio de 2010 para medir el núcleo y la utilización de subprocesos como medida de cómo una implementación del subprocesamiento mejora el rendimiento de la aplicación.

En este artículo, me centraré en una técnica más sofisticada de subprocesamiento múltiple denominada basada en tareas programación. Las tareas que le permiten repartir el trabajo de la aplicación para que todos o algunos de los núcleos de CPU disponibles. Con un poco de programación inteligente puede minimizar y incluso eliminar las restricciones de dependencia o tiempo de sincronización de datos.

Basándose en lo que aprendió en mi anterior artículo, echaremos es a través de una aplicación multiproceso más complejas que utiliza las tareas. Las tareas de permiten que la aplicación escalar a sí mismo en el número de núcleos disponibles y la cantidad de trabajo que debe llevar a cabo.

Un ratón en un laberinto

Cuando sentó a escribir este artículo, he intentado idear un problema que era complicado poner en paralelo pero aún más fáciles de ver lo que se está produciendo. De aciertos en la idea de crear una solución que debe resolver un laberinto 2D. Aunque a primera vista puede parecer algo trivial, es realmente bastante difícil de implementar correctamente, se puede saber porque tardé tres intentos de hacerlo bien.

La figura 1 muestra un laberinto simple y de serie de solver. La solución para el laberinto es sólo una ruta de larga, sinuous con muchas ramas que llevan a callejones. No es sorprendente que denominado algoritmo de solución “ mouse ”. El mouse (ratón) se observar su celda actual e intente ir a la celda siguiente. Si llega a un límite se intenta ir izquierdo. Si no se puede ir izquierdo intentará moverse. Si no, no se vaya a cualquiera de las direcciones, se marca la ruta de acceso actual como una solución de mensajes no enviados y realizar copias de seguridad.

image: Single-Threaded Maze

Figura 1 de un solo proceso Laberinto

Cuando el mouse se mueve en una nueva celda hace que la nota de las rutas que no utilizó. Por ejemplo, si un mouse (ratón) se puede mover hacia delante, pero también podía ir de izquierda, a continuación, recordará esa celda y esa dirección. Por lo tanto, cuando el mouse se desplace hacia abajo de un pasillo se tenga en cuenta entradas a ambos lados y en una pila de inserción. Cuando el mouse (ratón) llega a un extremo de mensajes no aparece ninguna de estas ubicaciones, realiza una de seguridad y dirija de la dirección guardada. Finalmente, llegará la persistencia de aunque enorme de extremo.

Después de que dispone de una copia de seguridad de un mouse (ratón), es necesario evitar que se trata de una dirección que ya ha buscado. Puedo hacerlo seleccionando una celda como visitado cuando se movió correctamente un mouse (ratón) en una celda. Por lo tanto, cuando se trata de un mouse (ratón) para desplazarse en una dirección nueva, comprueba primero que no hay ninguna pared. Si no hay ningún tipo de pared, se comprueba si se ha visitado antes de la celda que está considerando la posibilidad de mover a. Descartará cualquier movimiento en las celdas que ya se han visitado. Esto se ilustra en las rutas de sombreado en figura 1.

La solución de mouse

Esta solución es bastante fácil de ver y, por tanto, es fácil de entender la lógica detrás de la búsqueda. También es un poco hipnótico ver: al menos de unos instantes. Se muestra un diagrama de flujo simplificado para un mouse serie en figura 2.

image: Flow Chart of Mouse Actions

La figura 2 del diagrama de flujo de acciones de mouse

Aunque es muy fácil de entender el problema conceptualmente, hay algunos elementos sin restricciones a la que lo convierten en un reto. En primer lugar, no tiene idea cuánto tiempo un mouse (ratón) se ejecutará antes de que llegue a un extremo de mensajes no enviados. En segundo lugar, no tiene idea cuántas de las sucursales descubrirá a lo largo de la forma.

Este problema es doblemente interesante cuando intente ejecutarlo en varios subprocesos. La manera más sencilla de realizar varios núcleos de este problema descriptivo es realizar múltiples ratones y dar a cada mouse (ratón) en su propio subproceso, que es el enfoque he adoptado. Como ventaja añadida, esto mejora la visualización, porque puede cambiar el color activo de mouse (ratón) como un nuevo subproceso asume.

De hecho, fue un poco más complicada que originalmente se considera. Una vez que tenía una versión de un único subproceso de trabajo, se han efectuado el error de intentar adaptar esa versión y asegúrese de varios subprocesos. Se trataba de mi único error de arquitectura mayor. Pasó tres diferentes revisiones antes de que he superado hacia atrás y rehacer la arquitectura para que sea compatible con la tarea.

No trataré mis esfuerzos errores excepto al estado en que todos ellos se centran en la me intentar optimizar el rendimiento mediante la copia no los datos y al intentar minimizar la memoria y optimizar el acceso a los datos compartidos por los diversos subprocesos. Esencialmente, en el diseño original, tuve una pila global que podría bloquear, a continuación, debería insertar las ubicaciones de las ramas untaken en la pila mientras se ejecuta a través de ellos. Cuando un subproceso haya terminado el trabajo y ha ido en busca de más de trabajo para procesar, podría bloquear la pila (para evitar el acceso simultáneo por otro subproceso), muestre la ubicación y la dirección, a continuación, desbloquear la pila. Aunque esto funcionaba en cierta medida, era rudimentario y forzar que se considere la posibilidad de agregar datos nuevos en cada mouse (ratón) para realizar un seguimiento de la ruta de acceso lo que tendría una ruta de acceso desde el punto de partida para su ubicación actual.

Cuando se encuentra agregar mid-state información en un programa con subprocesamiento múltiple para compensar algún estado parcial que comienza con, o especiales de grafía de comportamiento o general haciendo algo que no es genérico para el código tasking y es el momento de reconsiderar su diseño.

Lo que terminado si lo hace se coloca la información de ruta de acceso actual de cada mouse (ratón) y hacer que esta parte de la información de inicialización del mouse en el. Cuando alcanza un punto de la sucursal, crea una estructura de datos del mouse (ratón) y lo inicializa desde la información del mouse actual, por lo que crea un clon mouse que, por ejemplo, va de izquierda derecho cuando el original. El clon contiene memoria del original en la. La única diferencia en cada mouse (ratón) es un contador que realiza el seguimiento del número de ratones creado: se trata cómo los ratones asigna un color.

Activa también acerca de cómo y realiza una copia global del Laberinto que contiene la información de estado de las celdas individuales ’ y ha no colocar los bloqueos de la escritura de la información de estado. Esto era una simplificación aceptará como un compromiso: un mouse (ratón) se va a trabajar en una ruta de acceso por sí mismo. Siempre se comprueba para ver si la celda está marcada como visitado antes de que lo traslada a la celda.

Puesto que no hay bloqueos no alrededor de los datos de la celda global, es posible, aunque improbable, que dos mouse es posible que ambos iniciar una ruta de acceso al mismo tiempo. Esto puede ocurrir si una entrada duplicada de la pila o a través de una ruta de un bucle que se ejecutan dos ratones entre sí. En cualquier caso, acepta el hecho de que un mouse (ratón), puede que esté ejecutando felizmente una ruta en su subproceso podría obtener suspendido y cuando se reanuda descubre que algunas otra mouse ha cruzado la ruta de acceso. En este caso el mouse (ratón) sólo copia como si ha llegado a una pared, ya que el mouse (ratón) que se extiende se sigue una ruta de acceso correctamente. El mouse (ratón) suspendido su oportunidad se ha perdido y hizo algún trabajo adicional.

Si hay muchos más procesamiento que participan en las celdas de marcadores, a continuación, podría ser más reacios a aceptar el hecho de que puede realizar un esfuerzo inútil. Eliminando los bloqueos de los datos compartidos simplemente significa que tengo que hacer que el algoritmo más robusto. Diseñar para controlar estas situaciones significa que hay menos espacio para que los errores. El origen más probable de los errores en programas multiproceso implica algún tipo de error, como, por ejemplo, una condición de anticipación de bloqueo o realizar suposiciones acerca de cuándo se actualizarán los contadores o de datos.

Si puede hacer que sus algoritmos suficientemente robustos como para controlar los datos que pueden ser ligeramente obsoletos y capaz de recuperarse de estos casos, ya está en la forma de realizar una arquitectura flexible de multiproceso.

Colas de tareas y dependencias

Windows Vista introduce nuevos algoritmos de programación y nuevos tipos primitivos que tienen el soporte subyacente para una serie de características de 4 de Microsoft .NET Framework. Una de estas características es la Task Parallel Library (TPL), que proporciona algunas de la más comunes paralelo algoritmos, como la bifurcación y combinación, en paralelo de robo de trabajo y la inclusión entre línea de la tarea de programación. Si está está programando en C++ no administrado, puede aprovechar de bloques de creación de subprocesamiento de Intel (TBB) o la biblioteca de patrones de Parallel (PPL) de Microsoft.

Estas bibliotecas incluyen las clases que proporcionan compatibilidad con la programación multiproceso para trabajos y las tareas. También tienen muchas clases de contenedor seguro para subprocesos. Estas clases se han probado y optimizado para el rendimiento, por lo que a menos que tenga deep-seated la necesidad de escribir una variación personalizada por alguna razón, será mejor que algunos probado el uso, un código sólido.

Debido a que se trata de un artículo de introducción sobre los subprocesos y tareas, y que es aconsejable algunas nociones de cómo funcionan estas bibliotecas de subprocesamiento, escribí mi propio conjunto de contenedores en torno a dos de las nuevas características en Windows Vista: ThreadPool y SlimReaderWriterLock (SRWLock). Se trata de dos formas de bajo costo de crear subprocesos de datos a prueba de errores para aquellas situaciones donde tiene un sistema de escritura y varios lectores y los datos normalmente no está bloqueados durante mucho tiempo. Tenga en cuenta que el objetivo de este artículo para que le guiará a través de la forma he elegido implementar un grupo de subprocesos que utiliza las tareas, las tareas que pueden tener dependencias. Para ilustrar los mecanismos básicos he tomado algunas libertades con el código para que sea más fácil de entender. El código funciona, pero es mejor elegir una de las bibliotecas de subprocesamiento para cualquier implementación real.

Para el algoritmo de laberinto que se decide utilizar la más genérica de los algoritmos de subprocesamiento múltiple: tareas que pueden tener dependencias (implementadas utilizando SRWLock) y un programador de tareas (uso de ThreadPool del sistema operativo). Es más genérico ya que básicamente sólo algunos bits del trabajo que debe hacer es de una tarea y el programador de tareas se ocupa de la comunicación con el sistema operativo para obtener la tarea que se ejecutan en un subproceso. Son genéricos porque es posible crear tareas fuera de cualquier código que se ejecutan y se produce en un objeto ThreadPool.

El desafío consiste en crear las tareas que ocupan tiempo suficiente para que intentan superar la sobrecarga de incitarles programado merece la pena. Si tiene tareas monolíticas grandes que tenga que se ejecutan, a continuación, puede crear algunos subprocesos y ejecute el código en ellas. Por otro lado, hay muchas aplicaciones donde existen grupos de tareas que deben hacer, algunos en serie, otras no. A veces podrá saber con antelación la cantidad de trabajo se debe realizar;otras veces, particularmente obtención o reaccionar ante la entrada del usuario o de algún tipo de comunicación, sólo está sondeando para algo que sólo ocasionalmente requieren procesamiento extendido. Esto se controla con facilidad por la naturaleza genérica de ThreadPool y su cola de la tarea asociada.

Personalización de ThreadPool

Para darle una idea clara de cómo crear un sistema tasking de ThreadPool, realmente sólo debe utilizar tres interfaces:

CreateThreadpool();
CloseThreadpool();
TrySubmitThreadpoolCallback();

Los dos primeros son sólo las funciones de Contabilidad. Básicamente, TrySubmitThreadpoolCallback toma un puntero a una función que ejecutar además de algunas variables de contexto. Se llama a esta función varias veces para cargar el grupo de subprocesos con las tareas de ejecución y le servirá de una manera de primero en el primero en salir (FIFO) (aquí no hay garantías).

Para que funcione con Mis tareas, escribí un contenedor breve sobre ThreadPool que me permite personalizar el número de subprocesos del grupo de subprocesos (consulte de figura 3). También he escrito una función de envío que se ocupará de realizar un seguimiento de las variables de contexto asociadas a la tarea.

La figura 3 del Contenedor de ThreadPool

class ThreadPool {
  PTP_POOL m_Pool;
public:
  static VOID CALLBACK WorkCallback(
    PTP_CALLBACK_INSTANCE instance,
    void* Context);
  ThreadPool(void);
  ~ThreadPool(void);
  
  static unsigned GetHardwareThreadsCount();

  // create thread pool that has optimal 
  // number of threads for current hardware
  bool create() { 
    DWORD tc = GetHardwareThreadsCount(); 
    return create(tc,tc); 
  }
  bool create(
    unsigned int minThreads, 
    unsigned int maxThreads = 0);
  void release();
  bool submit(ITask* pWork);
};

Lo interesante tener en cuenta es que normalmente sólo desea crear los subprocesos de software que hay subprocesos de hardware. A veces esto puede ser menos si tiene un subproceso principal fuera con otras tareas. Tenga en cuenta que el número de subprocesos que crea tiene nada que ver con el tamaño de la cola de tareas, es perfectamente legítima para crear un objeto ThreadPool con cuatro subprocesos y, a continuación, enviar cientos de tareas. Es probable que no es una buena idea, sin embargo, para tomar un solo trabajo de la serie y dividirla en miles de tareas. Se trata de una indicación de que tiene muchas tareas de nivel muy detalladas. Si se encuentra en esta situación, a continuación, simplemente will have crea una tarea cuyo trabajo es programar las tareas siguientes 100, o si está utilizando una de las bibliotecas tasking, a continuación, cree una tarea de robo de trabajo, procesos en línea o posterior.

Mi clase de tarea (Observe la mayúscula T, que usaré para denotar mi clase de contenedor de tareas, como se muestra en de figura 4) tiene la capacidad de depender de otras tareas. Ya que ThreadPool del sistema operativo no tiene esta capacidad, necesitaré agregarlo. Por lo tanto, cuando una tarea comienza a ejecutarse en un subproceso, lo primero que hace es la comprobación para asegurarse de que no tiene ninguna dependencia pendiente. Si es así, el subproceso de ejecución de los bloques de la tarea espera de la SRWLock. Sólo obtener volverá a programar la tarea cuando se libera el SRWLock.

La figura 4 del contenedor de la tarea

class ITask {
protected:
  vector<ITask*>    m_dependentsList;      // Those waiting for me
  ThreadSafe_Int32  m_dependencysRemaining;// Those I'm waiting for

  // The blocking event if we have dependencies waiting on
  SRWLOCK m_SRWLock; 
  CONDITION_VARIABLE m_Dependencies;

  void SignalDependentsImDone();
  ITask(); 
  virtual ~ITask();

public:  
  void blockIfDependenciesArePending();
  void isDependentUpon(ITask* pTask);

  unsigned int queryNumberDependentsRemaining()

  // A parent Task will call this
  void clearOneDependency();
  virtual void doWork(void*) = 0;
  virtual void* context() = 0;
};

De nuevo, permítame señalar que no es código que desea ver en una aplicación que no sean de académica, pero colocar el bloque aquí le permite ver exactamente lo que sucede. El sistema operativo se tenga en cuenta el bloque y programar la tarea de otro. Finalmente, a menos que haya un error de programación: la tarea bloqueada se desbloqueen y obtener a programar para ejecutarse.

Por lo general, no es una buena idea para programar una tarea que se obtenga inmediatamente suspendida. Debido a que la cola de ThreadPool tareas a y general es FIFO, desea programar las tareas que no han ninguna dependencia en primer lugar. Si estuviera escribiendo para un rendimiento óptimo en lugar de en modo de ejemplo, se podría agregar una capa que sólo se envía las tareas que no haya ninguna dependencia para el grupo de subprocesos. Puedo obtener inmediatamente con esto ya que finalmente obtener intercambia subprocesos bloqueados. En cualquier caso, debe tener alguna manera de subprocesos de señalización que se lleva a cabo una tarea y SRWLocks se puede utilizar para esta situación. Incorporarlos en mi clase de tarea es natural, en lugar de tener que escribir código de especiales para controlar cada caso.

Por diseño, una tarea puede tener cualquier número de dependencias de tareas. Normalmente, desea reducir o eliminar cualquier espera si se puede, y utilizando una herramienta como la lista de tareas de Visual Studio o analizadores de rendimiento de gráficos Intel le ayudará a realizar un seguimiento de éstos hacia abajo. La implementación que presento aquí es un sistema tasking muy básico y no se debe utilizar para el código que requiere de alto rendimiento. Se trata de código de recinto de seguridad adecuado para obtener sus pies multiproceso húmedos, pero debe buscar hacia TBB, TPL o PPL un código más eficaz.

ThreadPool llamará a la función WorkCallback, que se ejecuta un prefijo de código que consulta la estructura de datos de la tarea, por ejemplo:

VOID CALLBACK ThreadPool::WorkCallback(
  PTP_CALLBACK_INSTANCE instance, void* pTask) {

  ITask * pCurrentTask = (ITask*) pTask;
  pCurrentTask->blockIfDependenciesArePending( );
  pCurrentTask->doWork(pCurrentTask->context() );
  pCurrentTask->SignalDependentsImDone();
}

La operación básica es:

  1. ThreadPool carga el WorkCallback de su cola interna de la tarea.
  2. El código de consulta de la tarea para ver si hay alguna dependencia (dependencias primario). Si se detectan las dependencias, bloquear la ejecución.
  3. Una vez que no hay ninguna dependencia, llame a doWork, que es la parte real de que el código de tareas que es único en cada tarea.
  4. Al volver de doWork, desactive las dependencias de secundarios de esta tarea.

Lo importante que tener en cuenta es que existe algún código preámbulo y postscript que se encuentran en la clase ThreadPool que comprueba y borra las dependencias de la tarea. Cada subproceso obtiene ejecutar este código, pero tiene un único objeto Task asociados con él. Una vez que el código de preámbulo obtiene ejecutado, se llama la función de trabajo real de tareas.

Crear un contenedor de clases de tarea personalizada

El trabajo básico de una tarea es proporcionar algún tipo de contexto y un puntero a función para obtener la tarea que se ejecuta el grupo de subprocesos. Dado que deseaba crear tareas que eran capaces de hacer que las dependencias, necesitaba algo de código para controlar los registros de seguimiento de las dependencias de bloqueo y desbloqueo (consulte de figura 4).

Cuando se crea una tarea, a continuación, sabrá lo que es dependiente en otras tareas al proporcionar un puntero a la que depende de la tarea. Un objeto Task contiene un contador para el número de tareas que tiene que esperar a que, además de una matriz de punteros a las tareas que tengan que esperar.

Cuando una tarea tiene que no hay personas dependientes, se llamará la función de su trabajo. Una vez que se devuelve la función de trabajo, recorre todos los punteros de tarea de la matriz, una llamada a clearOneDependency en cada clase, que reduce el número de dependencias de restantes de dicha tarea. Cuando el número de dependencias de baja hasta cero, se libera el SRWLock, el subproceso que ejecuta la tarea espera de las dependencias de desbloqueo. El subproceso que ejecuta la tarea obtiene desbloqueado y la ejecución continuará.

Que es la información general básica de cómo diseñan las clases de tarea y ThreadPool. Terminado diseñarla de esta manera, porque el grupo de subprocesos nativos del sistema operativo no tiene bastante este comportamiento y me gustaría dar a la parte del código para reproducir con el que está en el control del mecanismo de dependencia. Que originalmente tenía un empaquetador mucho más complicado de ThreadPool que incluye una cola de prioridad, pero dado cuenta de que estaba innecesariamente complicar las cosas y que una relación de dependencia sencillo secundario-principal era todo lo que necesitaba. Si realmente desea echar un vistazo a la personalización de un programador de subprocesos, eche un vistazo a artículo de Joe Duffy, “ la creación de un grupo de subprocesos personalizado (parte 2): Un trabajo Stealing Queue, ” en tinyurl.com/36k4jcy de .

Tiendo a escribir código bastante defensiva. Tiendo a escribir implementaciones sencillas en las que trabajar, a continuación, refactorizar y aumentan la funcionalidad con controles frecuentes a lo largo de la forma de asegurarse de que yo no he desordenado nada. Por desgracia, también tienden a escribir código que se pasa por las referencias a otras variables, que es algo malo en un programa con subprocesamiento múltiple, si no se tiene cuidado.

Más de una vez se deshizo al convertir el código de la solución de un único subproceso Laberinto a uno de varios subprocesos. Por último, tuve que atraviesan y asegúrese de que estoy pasando copias de los datos cuando se ha producido una posibilidad de que el valor es modificado en un subproceso.

También he probado ser conservador a partir de una versión de un único subproceso que sólo guarda la ruta de acceso del mouse actual. Que presenta el problema de mantener un seguimiento de aún más datos de estado. Como se mencionó anteriormente, solucionó haciendo clic en mouse clon que tenía datos de todos sus elementos primarios ’. También se ha elegido eliminar el contenedor de ThreadPool por prioridad así como cualquier bloqueo de los datos de la celda de laberinto global. En todas las probabilidades presenté algunas tareas adicionales, pero también eliminan muchos de los orígenes de errores que se han producido por lo que simplifica enormemente el código.

El contenedor de ThreadPool y la clase de tarea funcionaban tal y como se ha diseñado. Utilizan estas clases en algunas pruebas unitarias para asegurarse de que muestra el comportamiento que estaba esperando. También instrumentado a través de los analizadores de rendimiento de gráficos Intel tareas de la herramienta, que tiene una característica que le permite etiquetar dinámicamente los subprocesos y examine los fragmentos de código se ejecutan en un subproceso concreto. Esta visualización de la ejecución de subprocesos permite comprobar que los subprocesos se estaban ejecutando, el bloqueo y sólo se vuelven a programar, como se esperaba.

Cuando rehacer el mouse para que sea de clones de sus padres, esto ha terminado simplificando en gran medida la requerida por la simulación, porque cada mouse es independiente de Contabilidad. Los únicos datos compartidos que ha terminado que requieren eran la matriz global de la celda, que indica si se ha visitado una celda. No resalte suficiente que es de vital importancia que tengan una buena visualización en cómo se programan las tareas.

El grupo de servidores de mouse

Se ha elegido el problema de laberinto debido a muestra una serie de problemas que puede recortar hacia arriba en la conversión de un algoritmo de un único subproceso en uno de varios subprocesos. La mayor sorpresa me era que, una vez que el bit toro y rewrote el algoritmo para eliminar algunos de los registros que había sido intentar mantener, el algoritmo de resolución de laberinto repentinamente está mucho más sencillo. De hecho, resultó más fácil que el algoritmo de un único subproceso porque se ha producido sin necesidad de mantener una pila de puntos de la sucursal, simplemente se han generado en un mouse nuevo.

Por diseño, cada mouse (ratón) era un duplicado de su elemento primario, por lo que cada mouse tenía un seguimiento de ruta de acceso heredados hasta el punto de inicio. El mouse no darse cuenta, pero escribí deliberadamente a los algoritmos de generación de laberinto para seleccionar la ruta de acceso más lejano posible. No tiene sentido en lo que facilita en ellos. El programa de prueba permite seleccionar entre varios algoritmos de generación de laberinto, que van desde el algoritmo original, que genera el error pasillos largos con ramas de vez en cuando finalmente se llevan a callejones: para algoritmos que son muy branchy con pasillos cortos. Ante estos mazes diferentes, la diferencia de comportamiento de la solución de un único subproceso a la solución de varios subprocesos puede ser bastante llamativo.

Cuando aplica un método multiproceso para resolver el algoritmo de laberinto original, reduce la duración de la búsqueda por ciento de 48 en un sistema de CPU de 4. Esto es debido a que el algoritmo tiene una gran cantidad de pasillos largos aparecen y no hay una gran cantidad de oportunidades para crear el mouse adicional (consulte de figura 5).

Figure 5 Solving the Original Long-Maze Algorithm

La figura 5 de solucionar el algoritmo de laberinto del valor de tipo Long Original

La figura 6 muestra un laberinto con ramas adicionales. Ahora hay muchas más oportunidades para crear el mouse y hacer que búsqueda al mismo tiempo. La figura 6 muestra la solución de subprocesos múltiples para este Laberinto corto de ruta de acceso, dónde se obtiene una reducción del tiempo para encontrar una solución en un 95% con más tareas.

Figure 6 Multiple Mice Make It Easy to Solve the Maze in Much Less Time

La figura 6 de mouse múltiples Make It Easy solucionar el laberinto en mucho menos tiempo

Esto sólo sirve para ilustrar que algunos problemas son más aptas para separar que otros. Me siento obligado a señalar que el programa de laberinto está diseñado para ser interesante a la vista, es decir, le permite ver el progreso de los ratones y los pasos a través de ellos. Si interesa basta con buscar la solución para el laberinto en la menor cantidad de tiempo, se podría desacopla la representación y el mouse, pero, a continuación, que no sería como divertido ver.

Subprocesos separados

Uno de los principales problemas que se ven cuando puedo ayudar a personas intentan realizar sus aplicaciones se ejecuten más rápidamente es una duda para intentarlo de subprocesamiento múltiple. Tengo entendido que duda. Cuando se agrega en el subprocesamiento múltiple, se agrega una capa de la complejidad de la mayoría de los programadores no se permite en un área que no tienen mucha experiencia con.

Por desgracia, cuando tímidas fuera de subprocesamiento múltiple, termina por abandonar una buena parte de la informática unutilized de energía.

Me he cubre los aspectos básicos del funcionamiento de un sistema tasking y le ofrece los fundamentos de cómo hacer para dividir los trabajos de gran tamaño en las tareas. Sin embargo, el enfoque actual, al tiempo que es correcto, no es lo más recomendable para obtener el máximo rendimiento en hardware multinúcleo actual y futuro. Si está interesado en obtener aún más las ganancias de rendimiento en hardware que se puede ejecutar la aplicación, a continuación, debe diseñar la aplicación con esto en cuenta.

La mejor manera de obtener el máximo rendimiento escalable de la aplicación es aprovechar las ventajas de una de las bibliotecas de paralelas existentes y ver la mejor opción para que se ajuste a las necesidades de su aplicación en las arquitecturas de diferentes que proporcionan estas bibliotecas. Las aplicaciones no administradas, en tiempo real o críticas para el rendimiento suele ser mejor se sirven a través de una de las interfaces proporcionadas en TBB, mientras que las aplicaciones administradas tienen una gran variedad de opciones de subprocesamiento múltiple en el 4 de .NET Framework. En cualquier caso, la elección de una de estas API de subprocesamiento determinará la estructura general de la aplicación y el diseño de las tareas para trabajar y coexistir.

En un próximo artículo, echaremos un vistazo a las implementaciones reales que sacar partido de estas técnicas y que muestren cómo construir aplicaciones alrededor de estas bibliotecas de varios subprocesos, por lo que puede diseñar sus propias implementaciones para utilizarlos.

En cualquier caso, ahora tiene los conocimientos básicos para probar algunos enfoques básicos de subprocesamiento y debe tener un vistazo a las bibliotecas de subprocesamiento para comenzar a calcular el mejor diseño de las aplicaciones futuras. Mientras el subprocesamiento múltiple puede suponer un reto, con una de estas bibliotecas escalables es la puerta de enlace para aprovechar el máximo rendimiento del hardware tanto actual como futuro.

Ron Fosner ha sido optimizar aplicaciones de alto rendimiento y los juegos de Windows durante años y está empezando a obtener el bloqueo de él. Es experto en gráficos y en optimización de Intel y se siente muy satisfecho cuando ve que todos los núcleos de una CPU se ejecutan a toda máquina. Puede ponerse en contacto con él en Ron@directx.com.

Gracias a los siguientes expertos técnicos de este artículo: Aaron Coday, Orion Granatir and Brad Werth