Java Para Cálculos Numéricos


A veces se utiliza Java para hacer cálculos numéricos intensivos a pesar de su fama de ser lento. Muchas veces la rapidez de cálculo es un aspecto importante, pero casi nunca el único a la hora de elegir el lenguaje para implementar estos cálculos. Java tiene muchas cosas atractivas para este tipo de aplicaciones como el hecho de generar programas portables, de tener muy buen soporte para programación paralela y tener «garbage collector» entre otros. Pero, ¿qué tan lento o rápido es realmente para este tipo de cálculos?

Para tener una idea decidí comparar Java con C que es el lenguaje rápido por excelencia aunque en este campo muchos consideran a Fortran como en rey de la computación numérica.

Para la comparación tomé el algoritmo de Chudnovsky, que calcula el número PI con la cantidad de dígitos que se quiera, mientras se disponga de memoria suficiente para almacenarlos.

Para el cálculo en C usé la biblioteca GMP que permite hacer cálculos con precisión arbitraria y se autoproclama como la implementación más rápida del cálculo de PI.

En Java usé la biblioteca Apfloat que también ofrece la posibilidad de hacer cálculos con precisión arbitraria y que promete ser más rápida que la implementación de BigDecimal de Java para números grandes.

En esta era de computadoras con múltiples núcleos, opté por realizar el cálculo utilizando todo el paralelismo disponible y para ello utilicé dos implementaciones paralelizadas del algoritmo en cuestión. Apfloat ya trae una implementación paralela del algoritmo de Chudnovsky como ejemplo de uso y la implementación de C está basada en OpenMP.

  • Algoritmo de Chudnovsky en C
  • Algoritmo de Chudnovsky en Java
  • Opciones de compilación de C: GCC: 4.7.2  -fopenmp -Wall -O2 -lgmp -lm
  • Versión de GMP 5.0.2
  • Versión de Apfloat 1.7.1
  • Versión Java: OpenJDK 1.7.0u15
  • Hardware: AMD Phenom X4 9550 4 GB RAM
  • Sistema operativo: Ubuntu 12.10 64 bits
  • Memoria disponible para Java 2 GB

Parámetros usados para Apfloat

  • builderFactory=org.apfloat.internal.LongBuilderFactory
  • defaultRadix=10
  • cacheL1Size=131072
  • cacheL2Size=524288
  • cacheBurst=64
  • memoryTreshold=402653184
  • sharedMemoryTreshold=268435456
  • blockSize=1048576
  • numberOfProcessors=4
  • filePath=
  • fileInitialValue=0
  • fileSuffix=.ap
  • cleanupAtExit=true

Resultados

Dígitos Apfloat (segundos)
GMP (segundos) Apfloat/GMP
100.000 0,435 0,041 10,61
1.000.000 3,987 0,595 6,70
10.000.000 67,821 8,044 8,43
100.000.000 745,732 111,824 6,67

Los resultados muestran que en esta prueba la versión Java llega a ser 6 veces más lenta que GMP.

Como Apfloat utiliza almacenamiento en disco cuando los números son más grandes que un cierto tamaño (configurable), elegir la configuración adecuada lleva un poco de pruebas. La configuración por omisión no está del todo optimizada para computadoras actuales con varios gigas de RAM.

Código de la Prueba

El código C es el citado más arriba.

Java

package calculatepi;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apfloat.Apfloat;
import org.apfloat.ApfloatRuntimeException;
import org.apfloat.samples.Operation;
import org.apfloat.samples.Pi;
import org.apfloat.samples.PiParallel;

public class App {

    public static void main(String[] args) {
        final int max = Integer.parseInt(args[0]);
        Writer nullWritter = new Writer() {
            @Override
            public void write(char[] chars, int i, int i1) throws IOException {
            }

            @Override
            public void flush() throws IOException {
            }

            @Override
            public void close() throws IOException {
            }
        };

        try (
                PrintWriter out = new PrintWriter(nullWritter);
                PrintWriter err = new PrintWriter(new File("/home/user/apfloatPierr.txt"))) {
            Pi.setOut(out);
            Pi.setErr(err);
            final Operation warmUpOp = new PiParallel.ParallelChudnovskyPiCalculator(1000000, 10);

            final List<Operation> ops = new ArrayList<>();

            for (int i = 100000; i <= max; i = i * 10) {
                ops.add(new PiParallel.ParallelChudnovskyPiCalculator(i, 10));
            }

            for (int i = 0; i < 5; i++) {
                PiParallel.run(1000000, 10, warmUpOp);
            }
            int digits = 100000;
            for (Operation op : ops) {
                err.println("====================================");
                PiParallel.run(digits, 10, op);
                digits = digits * 10;
            }

        } catch (IOException | ApfloatRuntimeException ex) {
            Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
}

Bash

#!/bin/sh

HEAP=2g
OUTFILE=apfloatPierr.txt
rm $OUTFILE

echo "Java..."

java -Xmx$HEAP -Xms$HEAP -jar /home/user/CalculatePi.jar 100000000
sleep 1

echo "C..."
echo "100000" >> $OUTFILE
./pi 100000 0 4 1> /dev/null 2>> $OUTFILE
echo "1000000" >> $OUTFILE
./pi 1000000 0 4 1> /dev/null 2>> $OUTFILE
echo "10000000" >> $OUTFILE
./pi 10000000 0 4 1> /dev/null 2>> $OUTFILE
echo "100000000" >> $OUTFILE
./pi 100000000 0 4 1> /dev/null 2>> $OUTFILE

2 pensamientos en “Java Para Cálculos Numéricos

  1. El warm up era calcular 5 veces pi con 100.000 dígitos. Luego se medía el tiempo para 10.000, 100.000, 1.000.000, etc.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s