2010-10-27 8 views

Respuesta

26

La clase Partitioner se utiliza para hacer las ejecuciones paralelas más grueso. Si tiene muchas tareas pequeñas para ejecutar en paralelo, la sobrecarga de invocar a los delegados para cada una puede ser prohibitiva. Al usar Partitioner, puede reorganizar la carga de trabajo en fragmentos y hacer que cada invocación paralela funcione en un conjunto ligeramente más grande. La clase abstrae esta característica y puede dividir en función de las condiciones reales del conjunto de datos y los núcleos disponibles.

Ejemplo: Imagine que desea ejecutar un cálculo simple como este en paralelo.

Parallel.ForEach(Input, (value, loopState, index) => { Result[index] = value*Math.PI; }); 

Eso invocaría al delegado para cada entrada en Entrada. Hacerlo agregaría un poco de sobrecarga a cada uno. Mediante el uso de Partitioner podemos hacer algo como esto

Parallel.ForEach(Partitioner.Create(0, Input.Length), range => { 
    for (var index = range.Item1; index < range.Item2; index++) { 
     Result[index] = Input[index]*Math.PI; 
    } 
}); 

Esto reducirá el número de invoca como cada invocación funcionará en un conjunto más amplio. En mi experiencia, esto puede aumentar significativamente el rendimiento al paralelizar operaciones muy simples.

+8

El rangeSize predeterminado es uno para Partitioner.Create(). Por lo tanto, la partición es la misma para ambos ejemplos de código. A menos que Partitioner.Create (0, Input.Length, i); donde i> 1, todavía tendrá el mismo número de hilo. – Pingpong

2

Para poner en paralelo una operación en una fuente de datos, uno de los pasos esenciales es dividir la fuente en varias secciones a las que se puede acceder simultáneamente por varios hilos. PLINQ y la Biblioteca paralela de tareas (TPL) proporcionan particionadores predeterminados que funcionan de forma transparente cuando se escribe una consulta paralela o un bucle ForEach. Para escenarios más avanzados, puede conectar su propio particionador.

Leer más here:

+2

que me gustaría añadir a eso: En términos generales * * que no lo usa. PLINQ lo hace, y probablemente se saldrá con la suya con los particionadores por defecto. –

1

La partición de rango, como sugiere Brian Rasmussen, es un tipo de partición que se debe usar cuando el trabajo requiere mucha CPU, tiende a ser pequeño (en relación con una llamada a método virtual), se deben procesar muchos elementos y mayormente constante cuando se trata de tiempo de ejecución por elemento.

El otro tipo de partición que se debe considerar es la partición de fragmentos. Este tipo de particionamiento también se conoce como un algoritmo de equilibrio de carga porque un subproceso de trabajo rara vez se quedará inactivo mientras haya más trabajo por hacer, que no es el caso para una partición de rango.

Se debe usar una partición de fragmento cuando el trabajo tiene algunos estados de espera, tiende a requerir más procesamiento por elemento o cada elemento puede tener tiempos de procesamiento de trabajo significativamente diferentes.

Un ejemplo de esto podría ser la lectura en la memoria y el procesamiento de 100 archivos con tamaños muy diferentes. Un archivo de 1K se procesará en mucho menos tiempo que un archivo de 1mb. Si se utiliza una partición de rango para esto, algunos subprocesos podrían permanecer inactivos durante un tiempo porque procesaron archivos más pequeños.

A diferencia de una partición de rango, no hay forma de especificar el número de elementos que cada tarea debe procesar, a menos que escriba su propio particionador personalizado. Otra desventaja de usar una partición de fragmento es que puede haber algo de contención cuando vuelve a obtener otro fragmento, ya que se usa un bloqueo exclusivo en ese punto. Por lo tanto, es evidente que una partición de fragmento no debe utilizarse para pequeñas cantidades de trabajo intensivo de CPU.

El particionador de fragmentos predeterminado comienza con un tamaño de fragmento de 1 elemento por fragmento. Después de que cada subproceso procesa tres trozos de 1 elemento, el tamaño del trozo se incrementa a 2 elementos por porción.Después de que tres subprocesos de 2 elementos han sido procesados ​​por cada subproceso, el tamaño del trozo se incrementa de nuevo a 3 elementos por porción, y así sucesivamente. Al menos esta es la forma en que funciona de acuerdo con Dixin Yan, (consulte la sección de partición de Chunk) que trabaja para Microsoft.

Por cierto, la herramienta de visualización agradable en su blog parece ser el Concurrency Visualizer profile tool. El docs for this tool afirma que se puede usar para localizar cuellos de botella de rendimiento, infrautilización de CPU, contención de hilos, migración de hilos cruzados, retrasos de sincronización, actividad de DirectX, áreas de E/S superpuestas y otra información. Proporciona vistas de datos gráficos, tabulares y textuales que muestran las relaciones entre los hilos en una aplicación y el sistema como un todo.

Otros recursos:

MSDN: Custom Partitioners for PLINQ and TPL

Part 5: Parallel Programming - Optimizing PLINQ por Joseph Albahari

Cuestiones relacionadas