2010-06-05 18 views
14

Estoy escribiendo un libro sobre programación multinúcleo usando .NET 4 y tengo curiosidad por saber qué partes de la programación multinúcleo a las personas les resulta difícil asimilar o anticipan que es difícil asimilarlas.Programación multinúcleo: las partes duras

+0

hay una casilla de verificación "wiki comunitario" en la esquina inferior derecha cuando edita la publicación. – kennytm

+3

no es necesario que wiki su publicación si no desea. Wiki significa "editable por cualquier persona" y que desea que la comunidad asuma la propiedad de su publicación, no se utiliza para clasificar preguntas "subjetivas" o de "discusión" ya que tenemos etiquetas por esa razón. – Juliet

+2

Creo que las preguntas sin una respuesta correcta son buenas candidatas para ser wikis comunitarios porque eso también permite que las personas editen las respuestas. – Gabe

Respuesta

3

Para comprender: detalles de memoria de bajo nivel como la diferencia entre la semántica de adquisición y liberación de la memoria.

La mayoría del resto de conceptos e ideas (cualquier cosa puede intercalarse, condiciones de carrera, ...) no son tan difíciles con un poco de uso.

Por supuesto, la práctica, especialmente si algo falla a veces, es muy difícil ya que necesita trabajar en múltiples niveles de abstracción para entender lo que está pasando, así que mantenga su diseño simple y diseñe la necesidad para el bloqueo, etc. (por ejemplo, utilizando datos inmutables y abstracciones de mayor nivel).

2

No son tanto los detalles teóricos, sino más los detalles prácticos de implementación que hacen tropezar a la gente.

¿Cuál es el problema con las estructuras de datos inmutables?

Todo el tiempo, la gente intenta actualizar una estructura de datos de múltiples hilos, resulta demasiado duro, y alguien interviene, y por lo que nuestro codificador persistente escribe este "utilizar estructuras de datos inmutables!":

ImmutableSet set; 

ThreadLoop1() 
    foreach(Customer c in dataStore1) 
     set = set.Add(ProcessCustomer(c)); 

ThreadLoop2() 
    foreach(Customer c in dataStore2) 
     set = set.Add(ProcessCustomer(c)); 

El codificador ha escuchado durante toda su vida que las estructuras de datos inmutables se pueden actualizar sin bloqueo, pero el nuevo código no funciona por razones obvias.

Incluso si sus académicos de orientación y desarrolladores experimentados, un pequeño manual básico sobre los conceptos básicos de programación inmutable no puede doler.

¿Cómo se reparten aproximadamente las mismas cantidades de trabajo entre subprocesos?

Este paso es correcto difícil. A veces se divide un solo proceso en 10,000 pasos que se pueden ejecutar en paralelo, pero no todos los pasos toman la misma cantidad de tiempo. Si divide el trabajo en 4 subprocesos, y los primeros 3 subprocesos finalizan en 1 segundo, y el último subproceso tarda 60 segundos, su programa multiproceso no es mucho mejor que la versión de subproceso único, ¿verdad?

Entonces, ¿cómo particionar los problemas con aproximadamente la misma cantidad de trabajo entre todos los hilos? Un montón de buenas heurísticas para resolver los problemas de embalaje del contenedor deberían ser relevantes aquí ...

¿Cuántos hilos hay?

Si su problema es muy paralelizable, agregar más hilos debería hacerlo más rápido, ¿no? Bueno, en realidad no, muchas cosas a considerar aquí:

Incluso un solo procesador central, agregar más hilos puede hacer que un programa sea más rápido porque más hilos ofrecen más oportunidades para que el sistema operativo programe su hilo, por lo que obtiene más tiempo de ejecución que el programa de un solo subproceso. Pero con la ley de rendimientos decrecientes, agregar más hilos aumenta el cambio de contexto, por lo que en cierto punto, incluso si su programa tiene más tiempo de ejecución, el rendimiento podría ser aún peor que la versión de un solo subproceso.

Entonces, ¿cómo se escuchan solo los hilos para minimizar el tiempo de ejecución?

Y si hay muchas otras aplicaciones que crean hilos y compiten por recursos, ¿cómo detecta cambios de rendimiento y ajusta su programa automágicamente?

+0

Jon es un gran defensor de la programación funcional, que realmente cambia las tareas que son difíciles. Supongo que te perdiste la etiqueta "f #" en la pregunta, debería haber señalado su enfoque funcional. El empaquetado de contenedores puede ser un enfoque para la creación de particiones, pero el robo de trabajo y las colas de trabajo, donde la partición no está determinada a priori, parecen ser mucho más populares en el mundo real. –

+0

@Ben: Tenga en cuenta que aboguen por los lenguajes funcionales impuros como F # y no eviten la mutación y las estructuras de datos mutables en absoluto. De hecho, creo que la mutación es a menudo esencial en el contexto del paralelismo. –

5

Ya que escribe un libro completo para programación multi-core en .Net.

Creo que también puede ir más allá de varios núcleos un poco.

Por ejemplo, puede usar un capítulo sobre computación paralela en un sistema distribuido en .Net. Poco probable, todavía no hay frameworks maduros en .Net. DryadLinq es el más cercano. (Por otro lado, Hadoop y sus amigos en la plataforma Java son realmente buenos.)

También puede usar un capítulo que demuestre algunos aspectos de la computación GPU.

6

Supongo que parte de esto depende de cuán básico o avanzado sea el libro/público. Cuando pasa de la programación de subproceso único a la de subprocesos múltiples por primera vez, generalmente se cae de un precipicio enorme (y muchos nunca se recuperan, consulte, por ejemplo, todas las preguntas confusas sobre Control.Invocar).

De todos modos, para añadir algunos pensamientos que son menos acerca de la propia programación, y más acerca de las otras tareas relacionadas en el proceso de software:

  • medición: decidir qué métrica que es el objetivo de mejorar, midiéndolo correctamente (es tan fácil medir accidentalmente lo incorrecto), usando las herramientas correctas, diferenciando señal versus ruido, interpretando los resultados y entendiendo por qué son como son.

  • Prueba: cómo escribir pruebas que toleran no determinismo/intercalaciones sin importancia, pero aún así precisar el comportamiento correcto del programa.

  • Depuración: herramientas, estrategias, cuando "difíciles de depurar" implica retroalimentación para mejorar su código/diseño y mejor estado mutable partición, etc.

  • física frente lógica afinidad hilo: la comprensión de la Hilo de GUI, entendiendo cómo, por ejemplo un F # MailboxProcessor/agent puede encapsular el estado mutable y ejecutarse en varios hilos, pero siempre con un solo hilo lógico (un contador de programa).

  • Patrones (y cuando se aplican): tenedor-join, mapa-reducir, productores y consumidores, ...

espero que habrá una gran audiencia para, por ejemplo, "ayuda, tengo una aplicación de un único subproceso con un 12% de utilización de CPU, y quiero aprender lo suficiente para hacerlo 4 veces más rápido sin mucho trabajo" y un público más reducido para, por ejemplo, "mi aplicación escala de forma sub-lineal a medida que agregamos núcleos porque parece que hay contienda aquí, ¿hay un mejor enfoque para usar?", por lo que un poco del desafío puede estar sirviendo a cada uno de esos públicos.

+0

Con respecto a las pruebas, mi empresa ha trabajado en la herramienta CHESS de Microsoft (http://research.microsoft.com/en-us/projects/chess/) para probar de manera exhaustiva todos los entrelazados de nuestro código multiproceso, y ha sido muy impresionante desde Empezar a acabar. Podría ser útil aquí. – Juliet

7

¿Qué es una unidad de trabajo útil para paralelizar y cómo puedo encontrar/organizar una?

Todas estas primitivas de paralelismo no son útiles si bifurca una pieza de trabajo que es más pequeña que la sobrecarga de bifurcación; de hecho, eso te compra una buena ralentización en lugar de lo que estás esperando.

Así que uno de los grandes problemas es encontrar unidades de trabajo que son obviamente más caras que las primitivas de paralelismo. Un problema clave aquí es que nadie sabe cuánto cuesta ejecutar, incluidas las primitivas de paralelismo. Claramente calibrar estos costos sería muy útil. (Como comentario adicional, diseñamos, implementamos y usamos diariamente un lenguaje de programación paralelo, PARLANSE, cuyo objetivo era minimizar el costo de las primitivas de paralelismo permitiendo al compilador generarlas y optimizarlas, con el objetivo de realizar pequeños trabajos ". más paralelizable ").

Uno también podría considerar la discusión de notación Big-Oh y sus aplicaciones. Todos esperamos que las primitivas de paralelismo hayan costado O (1). Si ese es el caso, entonces si encuentra trabajo con costo O (x)> O (1), entonces ese trabajo es un buen candidato para la paralelización. Si su trabajo propuesto también es O (1), entonces si es efectivo o no depende de los factores constantes y volvemos a la calibración como se indicó anteriormente.

Existe el problema de recolectar trabajo en unidades lo suficientemente grandes, si ninguna de las piezas es lo suficientemente grande. Code motion, algorithm replacement, ... son todas ideas útiles para lograr este efecto.

Por último, está el problema de la sincnonización: cuando mis unidades paralelas tienen que interactuar, ¿qué primitivas debo usar y cuánto cuestan esas primitivas ? (¡Más de lo que espera!).

4

Una cosa que me ha sorprendido es qué enfoque utilizar para resolver un tipo particular de problema. Hay agentes, hay tareas, cálculos asincrónicos, MPI para la distribución, para muchos problemas podrías usar múltiples métodos pero tengo dificultades para entender por qué debo usar uno sobre otro.

1

Encuentro que las concepciones de datos sincronizados que se mueven a través de nodos de trabajadores en patrones complejos son muy difíciles de visualizar y programar.

Por lo general, creo que la depuración también es un oso.

Cuestiones relacionadas