Ejemplo Benchmarking
Ejemplo Benchmarking¶
/*
* This Scala source file was generated by the Gradle 'init' task. */package taller
import org.scalameter._
import common._
import org.scalameter.Warmer.Default
object App {
def main(args: Array[String]): Unit = {
// Se crea un arreglo de 100 millones de enteros para realizar las pruebas de rendimiento
val arr = (1 to 100000000).toArray
// Medición del tiempo de ejecución secuencial (un solo hilo)
val t1 = withWarmer(new Default) measure {
val res = sumaArray(arr,0, arr.length)
//println(res)
}
println("Tiempo secuencial "+t1)
// Medición del tiempo de ejecución con 2 hilos (paralelismo)
val t2 = withWarmer(new Default) measure {
val m = arr.length/2
val (r1,r2) = parallel(
sumaArray(arr,0, m) ,
sumaArray(arr,m, arr.length)
)
//println(r1+r2)
}
println("Tiempo con 2 hilos "+t2)
// Medición del tiempo de ejecución con 4 hilos (paralelismo)
val t3 = withWarmer(new Default) measure {
val n = arr.length
val (r1,r2,r3,r4) = parallel(
sumaArray(arr,0, n/4) ,
sumaArray(arr,n/4, n/2),
sumaArray(arr,n/2, 3*n/4),
sumaArray(arr,3*n/4, n)
)
//println(r1+r2+r3+r4)
}
println("Tiempo con 4 hilos "+t3)
}
// Función que suma los elementos de un arreglo en un rango [s, t)
// Utiliza recursión de cola para eficiencia
def sumaArray(arr:Array[Int], s:Int, t:Int):Long= {
@scala.annotation.tailrec
def filtroArrayAux(i:Int, acc:Long):Long = {
if (i >= t) acc
else filtroArrayAux(i+1, arr(i)+acc)
}
filtroArrayAux(s,0L)
}
}
Al ejecutar encontramos
Tiempo secuencial 26.420437 ms
Tiempo con 2 hilos 15.240116 ms
Tiempo con 4 hilos 13.921229 ms
Conceptos teóricos involucrados¶
- Benchmarking: Proceso de medir el rendimiento de un programa bajo condiciones controladas. En este caso se usa la librería
scalameterpara obtener mediciones precisas. - Paralelismo: Ejecución simultánea de tareas para aprovechar múltiples núcleos del procesador. Se usa la función
paralleldel paquetecommonpara dividir el trabajo. - Speedup: Relación entre el tiempo secuencial y el tiempo paralelo. Para 2 hilos: \(26.42 / 15.24 \approx 1.73\), para 4 hilos: \(26.42 / 13.92 \approx 1.90\).
- Ley de Amdahl: Establece que la ganancia máxima de rendimiento está limitada por la porción secuencial del programa. En este caso, la suma es altamente paralelizable, pero la sobrecarga de creación de hilos y sincronización reduce la eficiencia.
- Recursión de cola: Técnica de programación funcional que permite que el compilador optimice la recursión evitando desbordamiento de pila. Se usa
@scala.annotation.tailrecpara garantizar esta optimización.
Tabla de resumen de conceptos¶
| Concepto | Descripción |
|---|---|
| Benchmarking | Medición del tiempo de ejecución de un programa bajo condiciones controladas |
| Paralelismo | Ejecución simultánea de tareas en múltiples núcleos |
| Speedup | Relación entre tiempo secuencial y tiempo paralelo |
| Ley de Amdahl | Límite teórico de la ganancia de rendimiento debido a la parte secuencial |
| Recursión de cola | Optimización de recursión para evitar desbordamiento de pila |
| ScalaMeter | Librería para realizar benchmarks precisos en Scala |
Función parallel |
Función que ejecuta tareas en paralelo y devuelve una tupla con los resultados |
Comentarios adicionales¶
- La ganancia de rendimiento al pasar de 2 a 4 hilos es pequeña (de 15.24 ms a 13.92 ms), lo que sugiere que el cuello de botella no es solo computacional, sino también de acceso a memoria (el arreglo es grande y puede haber contención en el bus de memoria).
- Para obtener un mejor speedup, se podría considerar un enfoque de fork-join más sofisticado o usar estructuras de datos que permitan mejor localidad de caché.
- El uso de
withWarmer(new Default)calienta la JVM antes de medir, lo que evita que los resultados se vean afectados por la compilación Just-In-Time (JIT).