2010-08-15 8 views
14

En particular, estoy buscando usar TPL para iniciar (y esperar) procesos externos. ¿El TPL considera la carga total de la máquina (tanto CPU como E/S) antes de decidir iniciar otra tarea (por lo tanto, en mi caso, otro proceso externo)?¿La biblioteca de tarea paralela (o PLINQ) tiene en cuenta otros procesos?

Por ejemplo:

Tengo alrededor de 100 archivos multimedia que necesitan ser codificada o transcodificado (por ejemplo, de WAV a FLAC o de FLAC a MP3). La codificación se realiza iniciando un proceso externo (por ejemplo, FLAC.EXE o LAME.EXE). Cada archivo tarda unos 30 segundos. Cada proceso está principalmente vinculado a CPU, pero hay algunas E/S allí. Tengo 4 núcleos, por lo que el peor de los casos (la transcodificación conectando el decodificador al codificador) solo utiliza 2 núcleos. Me gustaría hacer algo como:

Parallel.ForEach(sourceFiles, 
    sourceFile => 
     TranscodeUsingPipedExternalProcesses(sourceFile)); 

¿Esta puntapié inicial de 100 tareas (y, por tanto, 200 procesos externos que compiten por la CPU)? ¿O verá que la CPU está ocupada y solo hace de 2 a 3 a la vez?

+0

Por supuesto, lo que probablemente debería hacer es cablear un 'TaskCompletionSource' hasta el evento' Process.Exited', y luego tener un método 'TranscodeAsync' que devuelve' Task'. Entonces sería no bloqueante. Entonces puedo tener un control más detallado sobre las tareas sin perder el grano del TPL. –

Respuesta

21

Te encontrarás con un par de problemas aquí. El mecanismo de evitación de inanición del planificador verá sus tareas bloqueadas mientras esperan los procesos. Le resultará difícil distinguir entre un hilo estancado y uno simplemente esperando a que se complete un proceso. Como resultado, puede programar nuevas tareas si sus tareas se ejecutan o durante un tiempo prolongado (consulte a continuación). La heurística de ascenso de colina debería tener en cuenta la carga general del sistema, tanto desde su aplicación como desde otras. Simplemente intenta maximizar el trabajo realizado, por lo que agregará más trabajo hasta que el rendimiento general del sistema deje de aumentar y luego retrocederá. No creo esto afectará a su aplicación, pero el problema de evitación de la estabilización probablemente lo hará.

Puede encontrar más detalles en cuanto a cómo funciona todo esto en Parallel Programming with Microsoft®.NET, Colin Campbell, Ralph Johnson, Ade Miller, Stephen Toub (un proyecto anterior es online).

"El grupo de subprocesos .NET gestiona automáticamente el número de trabajadores hilos en la piscina Se añade y elimina las discusiones de acuerdo a una función de heurística El grupo de subprocesos de .NET tiene dos mecanismos principales para inyectar hilos:.. Una mecanismo de inanición evitar que añade trabajador hilos si se ve que no se avanza en elementos en cola y un heurística hillclimbing que trata de maximizar el rendimiento mientras se utiliza como unos hilos como sea posible.

El objetivo de evitar la inanición es prevenir deadlock. Este tipo de interbloqueo puede ocurrir cuando un trabajador th lectura espera un evento de sincronización que solo puede ser satisfecho por un elemento de trabajo que todavía está pendiente en las colas globales o locales del grupo de subprocesos. Si hubiera un número fijo de subprocesos de trabajo, y todos esos subprocesos estuvieran igualmente bloqueados , el sistema no podría realizar ningún progreso adicional. Agregar un nuevo hilo de trabajador resuelve el problema.

Un objetivo de la heurística de ascenso es mejorar la utilización de los núcleos cuando los hilos están bloqueados por E/S u otras condiciones de espera que detienen el procesador. De forma predeterminada, el grupo de subprocesos administrados tiene un subproceso de trabajo por núcleo. Si uno de estos subprocesos de trabajo se convierte en bloqueado, existe la posibilidad de que un núcleo esté subutilizado, dependiendo de en la carga de trabajo general de la computadora.La lógica de inyección de subprocesos no distingue entre un subproceso que está bloqueado y un subproceso que realiza una operación larga y con gran intensidad de procesador. Por lo tanto, siempre que las colas globales o locales del grupo de subprocesos contienen elementos de trabajo pendientes, los elementos de trabajo activos que tardan mucho tiempo en ejecutarse (más de medio segundo) pueden desencadenar la creación de nuevos subprocesos de subprocesador.

El grupo de subprocesos .NET tiene la oportunidad de inyectar subprocesos cada vez que finaliza un elemento de trabajo o en intervalos de 500 milisegundos, lo que sea es más corto. El grupo de subprocesos usa esta oportunidad para intentar agregar subprocesos (o quitárselos), guiado por comentarios de cambios anteriores en el recuento de subprocesos. Si agregar hilos parece ayudar al rendimiento, el grupo de subprocesos agrega más; de lo contrario, reduce el número de subprocesos de trabajo . Esta técnica se llama heurística de alpinismo. Por lo tanto, una razón para mantener cortas las tareas individuales es evitar "detección de inanición", pero otra razón para mantenerlos cortos es dar al conjunto de subprocesos más oportunidades para mejorar el rendimiento por ajustando el recuento de subprocesos. Cuanto menor sea la duración de las tareas individuales , más a menudo el grupo de subprocesos puede medir el rendimiento y ajustar el recuento de hilos en consecuencia.

Para hacer esto concreto, considere un ejemplo extremo. Supongamos que tiene una simulación financiera compleja con 500 operaciones intensivas de procesador , cada una de las cuales toma diez minutos en promedio para completar. Si crea tareas de nivel superior en la cola global para cada de estas operaciones, encontrará que después de unos cinco minutos el grupo de subprocesos crecerá a 500 subprocesos de trabajo. La razón es que el grupo de subprocesos ve todas las tareas como bloqueadas y comienza a agregar nuevos subprocesos a razón de aproximadamente dos subprocesos por segundo.

¿Qué pasa con 500 hilos de trabajo? En principio, nada, si tiene , tiene 500 núcleos para usar y grandes cantidades de memoria del sistema . De hecho, esta es la visión a largo plazo de la computación paralela. Sin embargo, si no tiene tantos núcleos en su computadora, es en una situación donde muchos hilos compiten por segmentos de tiempo. Esta situación se conoce como sobresuscripción de procesador. Permitir que muchos subprocesos intensivos del procesador compitan por el tiempo en un único núcleo agrega carga de conmutación de contexto que puede reducir drásticamente el rendimiento total del sistema . Incluso si no se queda sin memoria, el rendimiento en esta situación puede ser mucho, mucho peor que en el cálculo secuencial. (Cada cambio de contexto toma entre 6,000 y 8,000 ciclos de procesador). El costo del cambio de contexto no es la única fuente de sobrecarga. Un subproceso administrado en .NET consume aproximadamente un megabyte de espacio de pila , independientemente de si se usa ese espacio para las funciones que se están ejecutando actualmente. Se requieren aproximadamente 200,000 ciclos de CPU para crear un nuevo hilo, y aproximadamente 100,000 ciclos para retirar un hilo. Estas son operaciones costosas.

Mientras sus tareas no se toman cada minuto, el algoritmo bajada del grupo de subprocesos con el tiempo se dará cuenta de que tiene demasiados hilos y recortar en su propio acuerdo.Sin embargo, si tiene tareas que ocupan una cadena de trabajo durante muchos segundos, minutos u horas, esa arrojará fuera de la heurística del grupo de subprocesos, y en ese punto usted debería considerar una alternativa.

La primera opción es descomponer la aplicación en tareas más cortas que se completen lo suficientemente rápido para que el grupo de subprocesos consiga controlar la cantidad de subprocesos para un rendimiento óptimo. Una segunda posibilidad es implementar su propio programador de tareas objeto que no realiza la inyección de hilos. Si sus tareas son de larga duración , no necesita un planificador de tareas altamente optimizado porque el costo de la programación será insignificante en comparación con el tiempo de ejecución de la tarea. El programa para desarrolladores de MSDN® tiene un ejemplo de una implementación de programador de tareas simple que limita el grado máximo de concurrencia. Para obtener más información, consulte la sección, "Lectura adicional", al final de este capítulo.

Como último recurso, puede utilizar el método SetMaxThreads a configurar la clase de ThreadPool con un límite superior para el número de subprocesos de trabajo , por lo general igual al número de núcleos (esta es la propiedad Environment.ProcessorCount) . Este límite superior se aplica para todo el proceso, incluyendo todos los dominios de aplicación "

+0

+1 La sobrecarga de 'Parallel.ForEach' que toma un objeto' ParallelOptions' con un 'MaxDegreeOfParallelism 'también podría ayudar. –

+0

+1 No encuentro una versión en línea del libro – Paparazzi

+0

El libro está aquí. http://msdn.microsoft.com/en-us/library/ff963553.aspx –

2

La respuesta corta es:.. No

Internamente, el TPL utiliza el estándar ThreadPool para programar sus tareas de modo que en realidad estás preguntando si. el ThreadPool toma en cuenta la carga de la máquina y no lo hace. Lo único que limita el número de tareas que se ejecutan simultáneamente es el número de subprocesos en el grupo de subprocesos, nada más.

¿Es posible que el informe de procesos externos volver a su aplicación una vez que estén listos? En ese caso, no tiene que esperar por ellos (manteniendo los hilos ocupados).

-1

Se ejecutó una prueba con TPL/ThreadPool para programar una gran cantidad de tareas realizando giros en bucle. Utilizando una aplicación externa, he cargado uno de los núcleos al 100% usando proc afinidad. La cantidad de tareas activas nunca disminuyó.

Mejor aún, ejecuté varias instancias de la misma aplicación habilitada para .NET TPL. El número de subprocesos para todas las aplicaciones fue el mismo, y nunca fue inferior al número de núcleos, a pesar de que mi máquina apenas era utilizable.

Dejando a un lado la teoría, TPL usa la cantidad de núcleos disponibles, pero nunca verifica su carga real. Una implementación muy pobre en mi opinión.

+0

Creo que TPL asigna a _minimum_ una cantidad de subprocesos igual al número de núcleos. Comprueba la carga y puede aumentar el número de subprocesos, pero no disminuirá el número de subprocesos por debajo de ese mínimo. –

+0

Eso es correcto. Ese es el valor predeterminado para el mínimo. Es una salida de policía.¿Esperas que los desarrolladores creemos un código que monitoree la carga total de la CPU, excluyendo nuestra aplicación en ejecución, y reduzcas el número mínimo y máximo de hilos consumidos por el grupo de subprocesos en consecuencia? ¿O deberíamos configurar manualmente el mínimo desde el principio a 1? ¿O tal vez 2? ¿O CPU Count/2? Y, por cierto, NO, NO controla la carga real de la CPU. Solo monitorea sus propios hilos. Eso es todo. – MoonStom

+0

Algo que se me ocurre es que TPL es codicioso, puede subir de grado hasta el punto de la sobre suscripción de la CPU si eso beneficia la tasa de ejecución de las tareas, entonces estás en lo correcto, TPL supervisa las tareas/seg y no la carga de la CPU, que tiene algunos inconvenientes. Mi punto es que no creo que Microsoft esperara que los desarrolladores cambiaran la afinidad del procesador mientras usaban TPL. –

Cuestiones relacionadas