¿Cómo puedo saber si un conjunto de códigos es un buen candidato para la paralelización?

7

¿Cómo puedo saber en una aplicación si hay partes que se beneficiarán con el uso de la biblioteca de tareas de C # (creo que es la biblioteca de procesamiento paralelo)?

Teniendo en cuenta que se han realizado otras optimizaciones, ¿cómo puedo saber si no es solo intentarlo? En C #, ¿puedo usar Visual Studio para este análisis?

    
pregunta johnny 04.01.2018 - 19:07

3 respuestas

3

Sugeriría que se trate de si los grandes trozos de trabajo se pueden ejecutar de forma independiente, en la mayoría de los casos, sin dar resultados diferentes. (A menos que las variaciones sean aceptables o buenas .)

Tres candidatos que buscaría:

  • iteraciones que consumen mucho tiempo en las que la orden de procesamiento no cambiará el resultado.
  • Descubres que DoWork() debe suceder antes de que necesites los resultados.
  • Descubres que DoWork() debe suceder y no necesitas resultados en absoluto.

En los tres casos, asegúrese de que el impacto en el estado compartido sea manejable o no exista.

Y, tenga en cuenta que estos son solo candidatos . Debe evaluar cuánto trabajo debe hacerse y qué tan enredado (o no enredado) es el trabajo con el resto del trabajo que la aplicación podría estar realizando. Y, entonces, idealmente compararía ambas soluciones.

Por ejemplo ...

Una iteración que consume mucho tiempo en la que no importa el orden de procesamiento.

Tienes un List<Int32> data masivo para el que necesitas calcular el promedio lo más rápido posible . Crear un promedio es principalmente una adición, que es asociativa . Por lo tanto, podemos realizar la adición en partes capaces de tareas:

avg(data[0..z]) == ( sum(data[0..n]) + sum(data[n..m]) .. sum(data[x..z]) ) / data.Count;

(En realidad, se puede desglosar de una manera más sofisticada, compatible con clústeres ; pero, es un ejemplo ...)

Sé que DoWork() debe suceder antes de que necesites sus resultados.

Estás renderizando un documento. En ese documento, tiene N componentes de varios tipos (imágenes, bloques de texto con formato, gráficos, etc.). Para simplificarlo, en este escenario, cada componente puede representarse a sí mismo completamente de forma independiente y devolver un componente gráfico al LayoutEngine. Y, el LayoutEngine debe esperar a que cada componente se renderice y devuelva sus dimensiones antes de que pueda ubicarlos y enviarlos al DisplayEngine ... o lo que sea.

No necesito los resultados de DoWork() en absoluto .

Ciertos tipos de registro.

Si está registrando las interacciones de la aplicación para respaldar los esfuerzos de marcado, por ejemplo, es posible que desee activar y desactivar esos eventos de registro. Si el registro tarda mucho tiempo o falla, no desea interrumpir la operación o el rendimiento de la aplicación.

    
respondido por el svidgen 04.01.2018 - 21:01
10

Sabes que el código es un buen candidato para la paralelización cuando se puede dividir en un conjunto de tareas "discretas" (es decir, independientes). Una tarea discreta es aquella que produce un resultado específico y no tiene efectos secundarios que compiten con otras tareas, es decir, un elemento de trabajo autónomo.

Considere este algoritmo paralelo de instrucción rápida recursiva :

private void QuicksortSequential<T>(T[] arr, int left, int right) 
where T : IComparable<T>
{
    if (right > left)
    {
        int pivot = Partition(arr, left, right);
        QuicksortSequential(arr, left, pivot - 1);
        QuicksortSequential(arr, pivot + 1, right);
    }
}

private void QuicksortParallelOptimised<T>(T[] arr, int left, int right) 
where T : IComparable<T>
{
    const int SEQUENTIAL_THRESHOLD = 2048;
    if (right > left)
    {
        if (right - left < SEQUENTIAL_THRESHOLD)
        {

            QuicksortSequential(arr, left, right);
        }
        else
        {
            int pivot = Partition(arr, left, right);
            Parallel.Do(
                () => QuicksortParallelOptimised(arr, left, pivot - 1),
                () => QuicksortParallelOptimised(arr, pivot + 1, right));
        }
    }
}

Observe el Parallel.Do en la parte inferior. Aquí es donde tiene lugar la paralelización; funciona porque ninguna de esas llamadas a QuicksortParallelOptimised interferirá entre sí.

Para paralelizar su propio código, debe reescribirlo de tal manera que pueda expresarse como una serie de funciones independientes que acepten algunos parámetros y devuelvan un Task<T> . A continuación, puede ejecutar esas funciones en paralelo y combinar los resultados al finalizar.

Lecturas adicionales
Cómo: Escribir un Parallel.ForEach Loop simple

    
respondido por el Robert Harvey 04.01.2018 - 19:26
0

un buen indicador es: si ejecuta código para hacer algo muy a menudo (por ejemplo, para cada archivo en una carpeta) y estas ejecuciones en particular son independientes entre sí. esta es una parte trivial, la "capa de función" pura.

la parte no trivial es la "capa de diseño": si desea poner en paralelo algo para acelerar, debe analizar su programa: ¿qué partes de su programa toman más tiempo? Tal vez usted puede encontrar funciones que se ejecutarán más a menudo de lo requerido? entonces usted puede rediseñar su aplicación con respecto a esto. (diseñar un programa con el objetivo de acelerar su programa puede causar una menor legibilidad. aquí debe reflexionar sobre el valor de la velocidad y la legibilidad y, con suerte, puede encontrar un compromiso).

    
respondido por el anion 04.01.2018 - 19:36

Lea otras preguntas en las etiquetas