multithreading performance scala scalability future

multithreading - ¿Por qué mis futuros de Scala no son más eficientes?



performance scalability (2)

Estoy ejecutando este código de Scala en un sistema Core2 de cuatro núcleos de 32 bits:

def job(i:Int,s:Int):Long = { val r=(i to 500000000 by s).map(_.toLong).foldLeft(0L)(_+_) println("Job "+i+" done") r } import scala.actors.Future import scala.actors.Futures._ val JOBS=4 val jobs=(0 until JOBS).toList.map(i=>future {job(i,JOBS)}) println("Running...") val results=jobs.map(f=>f()) println(results.foldLeft(0L)(_+_))

(Sí, sé que hay maneras mucho más eficientes de sumar una serie de enteros; es solo darle a la CPU algo que hacer).

Dependiendo de lo que establezca JOBS, el código se ejecuta en los siguientes tiempos:

JOBS=1 : 31.99user 0.84system 0:28.87elapsed 113%CPU JOBS=2 : 27.71user 1.12system 0:14.74elapsed 195%CPU JOBS=3 : 33.19user 0.39system 0:13.02elapsed 257%CPU JOBS=4 : 49.08user 8.46system 0:22.71elapsed 253%CPU

Me sorprende que esto realmente no alcance mucho más allá de 2 futuros "en juego". Hago un montón de código C ++ multiproceso y no tengo dudas de que obtendré un buen escalado de hasta 4 núcleos y veo una utilización de CPU> 390% si codificara este tipo de cosas con TBB de Intel o boost::threads (sería considerablemente más detallado por supuesto).

Entonces, ¿qué está pasando y cómo puedo obtener la escala a 4 núcleos que espero ver? ¿Está esto limitado por algo en scala o la JVM? Se me ocurre que en realidad no sé "dónde" se ejecutan los futuros de Scala ... ¿se genera un subproceso por futuro, o "Futuros" proporciona un grupo de subprocesos dedicado a ejecutarlos?

[Estoy usando los paquetes scala 2.7.7 de Debian / Squeeze en un sistema Lenny con sun-java6 (6-20-0lennny1).]

Actualizar:

Como se sugiere en la respuesta de Rex, recodifiqué para evitar la creación de objetos.

def job(i:Long,s:Long):Long = { var t=0L var v=i while (v<=10000000000L) { t+=v v+=s } println("Job "+i+" done") t } // Rest as above...

¡Esto fue mucho más rápido que tuve que aumentar significativamente el número de iteraciones para que se ejecutara durante cualquier cantidad de tiempo! Los resultados son:

JOBS=1: 28.39user 0.06system 0:29.25elapsed 97%CPU JOBS=2: 28.46user 0.04system 0:14.95elapsed 190%CPU JOBS=3: 24.66user 0.06system 0:10.26elapsed 240%CPU JOBS=4: 28.32user 0.12system 0:07.85elapsed 362%CPU

que es mucho más parecido a lo que esperaría ver (aunque el caso de los 3 trabajos es un poco extraño, con una tarea que se completa de forma sistemática unos segundos antes de los otros dos).

Empujándolo un poco más, en un i7 de cuatro núcleos y hiperprocesado, la última versión con JOBS=8 logra una aceleración de x4.4 vs JOBS = 1, con un uso de CPU de 571%.


Mi conjetura es que el recolector de basura está haciendo más trabajo que la adición en sí. Así que estás limitado por lo que el recolector de basura puede manejar. Intente ejecutar la prueba nuevamente con algo que no cree ningún objeto (por ejemplo, use un bucle while en lugar del rango / mapa / pliegue). También puede jugar con las opciones de GC paralelo si su aplicación real golpeará el GC con tanta fuerza.


Tratar

(i to 500000000 by s).view.map(_.toLong).foldLeft(0L)(_+_)

Se supone que la aplicación de la view (como entendí id) para evitar repeticiones repetidas y creación de objetos al proporcionar envoltorios simples.

Tenga en cuenta también que puede usar reduceLeft(_+_) lugar de fold.