Este reporte fue elaborado para el curso de Programación multinúcleo del Tecnológico de Monterrey, Campus Estado de México.
1. Introducción
Según [Wolfram], el factorial de un número N se define de manera iterativa como:
En esta práctica se calculó el factorial de un número muy grande en Java utilizando la clase java.math.BigInteger (ver el API de Java en [Oracle]). El objetivo consistía en resolver este problema de manera secuencial y usando threads de Java para obtener una solución paralela.
Hardware y software utilizado
Los programas se probaron en una computadora de escritorio con un procesador de dos núcleos Intel Pentium D de 3.00GHz y 2.4 GiB de memoria RAM. El sistema operativo utilizado fue Ubuntu 12.04 con un kernel de Linux 3.2.0-58. Los programas se compilaron usando el compilador de Java 1.7.0_04 de Oracle. |
2. Solución secuencial
El siguiente listado muestra un programa completo en Java que calcula de forma secuencial el factorial de 100,000:
/*--------------------------------------------------------------------
* Práctica 0: Factoriales en Java versión secuencial
* Fecha: 21-Ene-2014
* Autores:
* 1166611 Pepper Pots
* 1160611 Anthony Stark
*--------------------------------------------------------------------*/
package mx.itesm.cem.pmultinucleo;
import java.math.BigInteger;
public class SequentialFactorial {
public static void main(String[] args) throws InterruptedException {
final int n = 100000;
long timeStart = System.currentTimeMillis();
BigInteger result = BigInteger.ONE;
for (int i = 2; i <= n; i++) {
result = result.multiply(BigInteger.valueOf(i));
}
long timeEnd = System.currentTimeMillis();
System.out.printf("Resultado = %d, Tiempo = %.4f%n", result.bitCount(),
(timeEnd - timeStart) / 1000.0);
}
}
Dado que el factorial de 100,000 es extremadamente grande para imprimirlo (más de cuatrocientos mil dígitos), se utilizó el método bitCount para solo contar el número de bits igual a uno que tiene el valor binario del resultado.
Esta es la salida del programa:
Resultado = 708218, Tiempo = 34.0650
3. Solución paralela
La solución paralela en Java involucra usar dos threads. El primer thread se encarga de calcular la primera mitad de la multiplicaciones: 1 × 2 × 3 × … × 50,000. El segundo thread se encarga de calcular la otra mitad de las multiplicaciones: 50,001 × 50,002 × 50,003 × … × 100,000. Una vez que terminan ambos threads, se multiplican los resultados de ambos para obtener el resultado final. El siguiente listado contiene el programa completo:
/*--------------------------------------------------------------------
* Práctica 0: Factoriales en Java versión paralela con threads
* Fecha: 21-Ene-2014
* Autores:
* 1166611 Pepper Pots
* 1160611 Anthony Stark
*--------------------------------------------------------------------*/
package mx.itesm.cem.pmultinucleo;
import java.math.BigInteger;
public class ParallelFactorial implements Runnable {
private int start, end;
private BigInteger result = BigInteger.ONE;
public ParallelFactorial(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public void run() {
for (int i = start; i <= end; i++) {
result = result.multiply(BigInteger.valueOf(i));
}
}
public static void main(String[] args) throws InterruptedException {
final int n = 100000;
long timeStart = System.currentTimeMillis();
ParallelFactorial p1 = new ParallelFactorial(2, n / 2);
ParallelFactorial p2 = new ParallelFactorial(n / 2 + 1, n);
Thread t1 = new Thread(p1);
Thread t2 = new Thread(p2);
t1.start();
t2.start();
t1.join();
t2.join();
BigInteger total = p1.result.multiply(p2.result);
long timeEnd = System.currentTimeMillis();
System.out.printf("Resultado = %d, Tiempo = %.4f%n", total.bitCount(),
(timeEnd - timeStart) / 1000.0);
}
}
El programa produce esta salida:
Resultado = 708218, Tiempo = 12.9380
El bit count es el mismo que en la versión secuencial, por lo que podemos suponer que nuestra versión paralela produce el resultado correcto.
4. Resultados
A continuación se muestran los tiempos de ejecución de varias corridas de los dos programas:
# de corrida | Tiempo T1 (segundos) |
---|---|
Media aritmética |
33.8350 |
1 |
33.9440 |
2 |
34.2370 |
3 |
33.4740 |
4 |
33.4550 |
5 |
34.0650 |
# de corrida | Tiempo T2 (segundos) |
---|---|
Media aritmética |
12.9868 |
1 |
13.1770 |
2 |
12.8350 |
3 |
12.6560 |
4 |
13.3280 |
5 |
12.9380 |
A partir de las medias aritméticas calculadas, el speedup obtenido con un CPU de dos núcleos es:
El speedup obtenido es muy bueno, incluso superando al speedup ideal. Sin embargo, no queda claro por qué ocurre esto.
5. Agradecimientos
Se agradece a Steve Rogeres por sugerir usar LibreOffice Calc para calcular los promedios de los tiempos de ejecución.
6. Referencias
-
[Oracle] Oracle Corporation. Java Platform, Standard Edition 7 API Specification http://docs.oracle.com/javase/7/docs/api/ (Consultada el 21 de enero, 2014).
-
[Wolfram] Wolfram MathWorld. Factorial http://mathworld.wolfram.com/Factorial.html (Consultada el 21 de enero, 2014).