2012-06-07 8 views
13

Java Hotspot puede optimizar el código secuencial muy bien. Pero estaba adivinando que con el advenimiento de las computadoras multi-core, la información en tiempo de ejecución puede ser útil para detectar oportunidades de paralelizar el código en tiempo de ejecución, por ejemplo, detectar que la canalización del software es posible en un bucle y cosas similares.¿Tiene la JVM la capacidad de detectar oportunidades de paralelización?

¿Se ha realizado algún trabajo interesante sobre este tema? ¿O es una falla de investigación o un problema de detención que es muy difícil de resolver?

+1

Normalmente, un ExecutorService o tenedor/JOIN es una mejor elección. Dado que el tiempo que lleva distribuir las tareas entre hilos es alto, es muy poco probable que un compilador automático sea mejor que un código escrito a mano. A menudo, para bucles simples, el uso de múltiples hilos es más lento. –

+0

Muchos otros compiladores hacen este tipo de cosas en estos días, no veo ninguna razón por la que una JVM/JIT no debería. –

+0

Desde mi experiencia práctica, la JVM es bastante buena en esto. – dagnelies

Respuesta

14

Creo que las garantías actuales de Java memory model hacen que sea bastante difícil hacer mucho, si es que lo hace, la paralelización automática en el compilador o nivel de VM. El lenguaje Java no tiene semántica para garantizar que cualquier estructura de datos sea incluso efectivamente inmutable, o que cualquier declaración particular sea pura y libre de efectos colaterales, por lo que el compilador tendría que resolverlos automáticamente para poder paralelizarlos. Algunas oportunidades elementales serían posibles de inferir en el compilador, pero el caso general quedaría en el tiempo de ejecución, ya que la carga dinámica y el enlace podrían introducir nuevas mutaciones que no existían en tiempo de compilación.

Considere el siguiente código:

for (int i = 0; i < array.length; i++) { 
    array[i] = expensiveComputation(array[i]); 
} 

sería trivial para paralelizar, si expensiveComputation es una pure function, cuya salida depende sólo de su argumento, y si pudiéramos garantizar que array no sería cambiado durante el bucle (en realidad lo estamos cambiando, estableciendo array[i]=..., pero en este caso particular, siempre se llama primero a expensiveComputation(array[i]), por lo que está bien aquí - asumiendo que array es local y no se hace referencia desde ningún otro lugar).

Por otra parte, si cambiamos el bucle de la siguiente manera:

for (int i = 0; i < array.length; i++) { 
    array[i] = expensiveComputation(array, i); 
    // expensiveComputation has the whole array at its disposal! 
    // It could read or write values anywhere in it! 
} 

continuación paralelización no es trivial, más aún si expensiveComputation es puro y no altera su argumento, porque los hilos paralelos se estar cambiando el contenido de array mientras que otros lo están leyendo! El paralelizador tendría que averiguar al que se refieren las partes de la matriz expensiveComputation en diversas condiciones, y sincronizar en consecuencia.

Tal vez no sería imposible de plano para detectar todas las mutaciones y los efectos secundarios que pueden estar pasando y tomar en cuenta los paralelización cuando, pero sería muy duro, a ciencia cierta, probablemente no factible en práctica. Esta es la razón por la que la paralelización, y el hecho de saber que todo sigue funcionando correctamente, es el dolor de cabeza del programador en Java.

Los lenguajes funcionales (por ejemplo, Clojure en JVM) son una buena respuesta a este tema. Las funciones puras sin efectos secundarios junto con las estructuras de datos persistent ("efectivamente inmutables") potencialmente permiten la paralelización implícita o casi implícita. Vamos a duplicar cada elemento de una matriz:

(map #(* 2 %) [1 2 3 4 5]) 
(pmap #(* 2 %) [1 2 3 4 5]) ; The same thing, done in parallel. 

Esto es transparente debido a 2 cosas:

  1. La función #(* 2 %) es puro: toma un valor en y da un valor fuera, y eso es todo. No cambia nada, y su salida depende solo de su argumento.
  2. El vector [1 2 3 4 5] es inmutable: no importa quién lo está mirando o cuándo es el mismo.

Es posible hacer funciones puras en Java, pero 2), la inmutabilidad, es el talón de Aquiles aquí. No hay matrices inmutables en Java. Para ser pedante, nada es inmutable en Java porque incluso los campos final se pueden cambiar utilizando la reflexión. Por lo tanto, no se puede garantizar que la salida (¡o entrada!) De un cálculo no se modifique por la paralelización -> entonces la paralelización automática es generalmente inviable.

El mudo "elementos duplicar" ejemplo se extiende a un procesamiento complejo arbitrariamente, gracias a la inmutabilidad:

(defn expensivefunction [v x] 
    (/ (reduce * v) x)) 


(let [v [1 2 3 4 5]] 
    (map (partial expensivefunction v) v)) ; pmap would work equally well here! 
+0

Esto ignora por completo el caso (relativamente común) de cálculos puramente aritméticos, que HotSpot debería ser capaz de paralelizar tan bien como cualquier otra persona. ¿Puede? –

+0

@Louis: ¿Qué quiere decir exactamente con "cálculos puramente aritméticos"? No sé si HotSpot lo hace, pero todos los procesadores modernos ya están haciendo [predicción de bifurcación] (http://en.wikipedia.org/wiki/Branch_predictor) y [optimización especulativa] (http: //en.wikipedia. org/wiki/Speculative_execution) así que no estoy seguro si HotSpot podría agregar algo a eso. –

+0

En lugar de llamar a los métodos 'expensiveComputation', ¿qué pasa con los bucles que simplemente hacen las matemáticas directamente en las matrices? –

Cuestiones relacionadas