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

Conceptos Básicos de Spring Security

Spring Security es el framework de autenticación y control de acceso de Spring.

Autenticación en el caso más usual es determinar que el usuario que accede al sistema es quien dice ser. Spring soporta muchísimas formas de hacer la autenticación y van desde la más básica que es un simple formulario HTML hasta otras como OpenID, CAS, LDAP, Kerberos, JAAS, etc.

Control de acceso es determinar si un usuario puede realizar una determinada acción dentro del sistema. En muchos casos no tiene sentido realizar el control de acceso usuario por usuario sino agrupando a todos los usuarios que pueden realizar las mismas acciones dentro del sistema. Es decir, no se define “María y Roberto pueden imprimir facturas” sino, “todos los usuarios que sean vendedores pueden imprimir facturas”. Si hoy Juan es encargado de limpieza entonces no puede imprimir facturas, pero el día de mañana, Juan podría ser ascendido a área de ventas y ser vendedor, por lo que entonces sí podrá imprimir facturas. La forma de agrupar a “todos los usuarios que sean vendedores” es mediante roles. Entonces en un momento sólo María y Roberto tienen el rol “ROLE_VENDEDOR” y posteriormente se agregará Juan a ese grupo asignándole dicho rol.

Entender que un rol no es otra cosa que un conjunto de usuarios es muy importante.

Spring permite controlar el acceso en tres niveles

  1. URLs de una aplicación WEB,
  2. métodos de un Bean de Spring.
  3. objetos del dominio.

La única diferencia entre las tres es qué operación es la que se está controlando.

En el primer caso, se busca controlar el acceso a un grupo de recursos que son identificables mediante una URL. Por ejemplo si nuestra aplicación web tiene una página que sólo debe ser accedida por un gerente y no por todos los demás empleados, una estrategia sería que la URL de esa página fuera por ejemplo “/gerencia/index.jsp” y se configurara Spring Security para que sólo usuarios con rol “ROLE_GERENTE” pudieran acceder a URLs que empezaran con “/gerencia”.

El segundo caso busca controlar la ejecución de un método en particular. Se usa en situaciones donde no se trata de una aplicación web, cuando no es posible tener URLs distintas para diferenciar el acceso o cuando independientemente de qué URL fue solicitada desde el cliente queremos asegurarnos de que no se ejecute un método si no se trata del usuario que tiene el permiso. Los métodos que pueden ser controlados son métodos de objetos que sean Beans de Spring, no cualquier objeto.

El tercer caso busca controlar el acceso a objetos de dominio. El ejemplo más claro es para extraer dinero de una cuenta bancaria. Se debe verificar que el usuario sea el dueño de esa cuenta en particular y la relación de quién es dueño de qué cuenta puede ir variando en el tiempo por ejemplo a medida que se agregan o quitan titulares. En este caso los objetos que se desean controlar no tienen que ser Beans de Spring, puede ser cualquier objeto.

Para resolver el tercer caso se usa un módulo de Spring Security que se llama Domain Object Security o Spring ACLs.

Con Spring ACLs se define para cada objeto (instancias individuales) del dominio que se necesite controlar una lista de usuarios y roles que pueden accederlo. Como un rol no es otra cosa que un conjunto de usuarios, en verdad lo que se indica para cada objeto del dominio es directa o indirectamente una lista de usuarios.

Gráficamente lo que se tiene es un objeto del dominio, como por ejemplo una cuenta bancaria:

{Cuenta 123541500542}

y asociada al objeto una lista de usuarios que están autorizados a sacar dinero de esa cuenta

{Cuenta 123541500542} -> {“Juan”, “Pedro”, “Susana”}

Cuando la aplicación está por realizar la operación de débito sobre esa cuenta debemos indicarle a SpringSecurity que verifique que el usuario que está realizando la operación esté en la lista de usuarios de la cuenta sobre la que se está operando.

¿Cómo sería esto con la intervención de roles? Supongamos que existiera en el sistema un rol “ROLE_GERENTE_DE_CUENTAS” y que de acuerdo a las reglas del banco un usuario con ese rol está autorizado a realizar débitos sobre cualquier cuenta. La ACL quedaría así

{Cuenta 123541500542} -> {“Juan”, “Pedro”, “Susana”, “ROLE_GERENTE_DE_CUENTAS”}

Spring Security verificará en el momento que le sea indicado si el usuario que está intentando realizar la operación es Juan, Pedro, Susana, o algún usuario que tenga asignado el rol “ROLE_GERENTE_DE_CUENTAS”. Nuevamente,  como un rol no es otra cosa que un conjunto de usuarios en la lista la presencia de un rol es sólo una forma indirecta de referirnos a usuarios.

Si en cambio se define a los roles como un conjunto de “operaciones” o un conjunto de “permisos” es más difícil entender qué hace el rol en la lista de usuarios autorizados y por lo tanto entender cómo adaptar Spring ACLs a nuestra aplicación.

Spring ACLs es muy flexible y potente ya que permite manejar varios permisos como lectura, escritura, borrado y además permite otorgar y revocar permisos. Por ejemplo si le otorgamos permiso al rol “ROLE_GERENTE” y se lo revocamos al usuario “Juan” que  tiene ese rol, lo que logramos es definir que todos los gerentes menos “Juan” tienen otorgado el permiso. También podría ser a la inversa y definiríamos que ningún gerente tenga permiso a excepción de “Juan”. Si vemos los roles  como conjuntos de usuarios, es simplemente quitar elementos del conjunto.

Para aprender más en detalle cómo usar Spring ACLs, se pueden consultar estos dos artículos

Formato de Números y Fechas con Spring Web MVC

import java.math.BigDecimal;
import java.util.Date;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;

// ...
    @DateTimeFormat(pattern = "dd/MM/yyyy")
    private Date control;

    @NumberFormat(style = NumberFormat.Style.NUMBER)
    @Digits(integer = 5, fraction = 5)
    private BigDecimal distantVisualAcuity;

    @Max(100)
    @Min(10)
    @NumberFormat(style = NumberFormat.Style.NUMBER)
    private BigDecimal totalInterpupilaryDistance;

    @NotNull
    @Past
    @DateTimeFormat(pattern = "dd/MM/yyyy")
    private Date date;

Spring Web MVC convierte los datos ingresados por el usuario al tipo de dato del Bean que respalda el formulario. No es necesario escribir una sola línea de código para convertir la enorme mayoría de los valores que se ingresan. Basta con usar algunas annotations muy sencillas. En el caso de números se puede establecer, por ejemplo, la cantidad de dígitos enteros y fraccionarios, sin tener que recurrir a una máscara de entrada.

Para poder usar la annotation DateTimeFormat hay que incorporar joda-time como dependencia.

La conversión funciona tanto para el ingreso de texto como para el formateo a la hora de editar el formulario.

Las annotations así usadas son además documentación del sistema. Cualquier persona que vea cómo está anotada la variable sabe el tipo de valores de entrada que admite sin tener que mirar otra cosa.

NetBeans: el IDE que se Autoanaliza

Botón para que Netbeans se autoanalice

Botón para que Netbeans se autoanalice

En los últimos a años el entorno de desarrollo NetBeans respondió a la competencia que representa Eclipse y mejoró en muchísimos aspectos, entre los que se destaca el soporte para diferentes lenguajes (hoy PHP, C/C++, Ruby y Groovy están perfectamente integrados a NetBeans). Pero un aspecto clave para muchos es el rendimiento y para lograr mejoras es fundamental identificar dónde hay margen para mejorar.

Así que NetBeans se puede autoanalizar (realizar un sampling profiling de si misma al vuelo) y mostrar los resultados desde su propia interfaz mientras la usamos. ¡Cuántas veces quisiera hacer eso mientras uso otros programas!

Resultado del Profiling de NetBeans

Resultado del Profiling de NetBeans

Realmente en las últimas versiones se han notado mejoras en rendimiento. Más tareas se realizan en segundo plano sin interferir al usuario y hay menos operaciones que tomen mucho tiempo.

Además el profiler que viene integrado con NetBeans es excelente. Lo he usado para analizar código que nunca antes había visto  y en nada de tiempo me permitió detectar los tres o cuatro métodos donde había mucho margen para hacer optimizaciones.

Incluso muchas veces importo proyectos de Eclipse con el único objetivo de pasarlos por el profiler de NetBeans.

Algunas de las Razones del Éxito de Java

Java es un lenguaje muy criticado desde todos los ángulos. Que es lento, que no sirve para cierta tarea, que no soporta tal cosa o que es el nuevo COBOL. Muchas críticas más. Sin embargo, con todos sus defectos es uno de los lenguajes de programación más exitosos. Pero, ¿por qué?

No hay un solo motivo, claro está, pero pienso que uno de los más importantes es retrocompatibilidad. Un programa hecho para la versión de Java 1.0 requiere ningún cambio para ejecutarse en una versión posterior de Java. Esto contrasta fuertemente con otros lenguajes que introducen cambios incompatibles con cada nueva versión, obligando a tener que ejecutarse con una determinada versión de su intérprete o máquina virtual.

Python

Python es un ejemplo. En este artículo se detallan algunas de las cuestiones que se deben tener en cuenta a la hora de escribir código que se piensa algún día migrar a Python 3.0. Esto hace que muchas bibliotecas de funciones queden atadas a cierta versión de Python, creando una situación muy poco deseada: para usar cierta biblioteca debo usar yo la versión de Python compatible o esperar a que migren la biblioteca.

PHP

PHP es el ejemplo más patético de este problema. Introdujeron varios cambios incompatibles de la versión 3 a la 4, de la 4 a la 5 y de la 5 a la 6. Y como cada nueva versión de PHP requiere que se escriba el código de otra forma, los frameworks tampoco se esfuerzan en mantener compatibilidad. Un programa hecho con Symfony 1.0 no funciona en ninguna versión posterior. En general un programa hecho con Symfony N va a requerir que se tenga instalada la versión N de Symfony (y la de PHP correspondiente) para siempre mientras esté en uso. Existe la posibilidad (desde Symfony 1.2) de migrar el proyecto, pero se requiere volver a probarlo, revisar que haya quedado todo bien y en general tener mucha suerte de no haber hecho nada que no esté contemplado por el migrador.

La clave para que Java haya podido lograr esa compatibilidad está en que los cambios que se introducen (que han sido muchísimos a lo largo de los años) son propuestos, analizados y aprobados por expertos en el área.

Otra ventaja de dar participación a expertos en las decisiones sobre el lenguaje favorece a que no se meta la pata tan seguido. Por ejemplo, Java desde el día 0 soporta Unicode. Esto facilitó su adopción en el mundo y le ahorró a todos los programadores innumerables problemas. Hoy, a más de 15 años de su primera versión,  Python 3.0 y PHP 6 finalmente están incorporando Unicode, claro que sumando una nueva incompatibilidad.

Finalizar Aplicación Java / Swing sin Recurrir a System.exit()

La máquina virtual Java termina su ejecución cuando todos los threads que están corriendo son demonios. En particular el thread main (en el que corre nuestro programa apenas arranca) no es un demonio, por lo que mientras se ejecuta, la JVM sigue corriendo.

Cuando abrimos un JFrame de Swing arranca el EDT que es otro thread que no es demonio.

Ningún thread creado por nuestro código es demonio a menos que se lo indique explícitamente con el método setDaemon(true).

Entonces cuando el usuario nos indica que quiere salir de nuestra aplicación o por cualquier circunstancia deseamos terminar la ejecución nos basta con hacer que tanto el thread main, el EDT y todos los thread que creamos terminen.

En general el thread main ya no se está ejecutando cuando arrancamos el EDT (ya se ejecutó la última instrucción del método main). Los thread que creamos deben llegar la última instrucción del método run (habrá que revisar la lógica de cada uno si es que hay algún bucle o está esperando recursos).

Pero ¿cómo finalizar el EDT, si es el thread que está permanentemente esperando los eventos del usuario?

El EDT finaliza cuando se destruye el último componente visual de nivel superior (JFrame o  JDialog) de Swing. Entonces, por ejemplo si nuestra aplicación es un JFrame, basta con hacer

this.setVisible(false);
this.dispose();

Si ese JFrame es el último (o único), entonces el EDT termina y la JVM también. Si hay más instancias de JFames o JDialog abiertas por ahí, habrá que hacerlo con cada una.

Recurrir a System.exit() para salir de la aplicación de escritorio es como usar el administrador de tareas del sistema operativo para cerrar los programas.

Si pensamos un poco en el flujo natural del control de nuestra aplicación, no lo necesitaremos ni siquiera para salir en caso de errores fatales.

La desventaja es que si tenemos varios System.exit() diseminados por todo el código no tenemos forma de asegurarnos que antes de salir, se haga determinada tarea (borrar archivos temporales, imprimir algún mensaje, pedir confirmación al usuario, etc.) Habrá que hacerla en cada punto donde se quiera salir.

Swing y el EDT

Una de las primeras cosas que hay que saber al empezar a programar aplicaciones de escritorio en Java con Swing es que el thread main que arranca cuando se ejecuta el método main de nuestra aplicación no es el mismo thread en el que se ejecutan los eventos que dispara el usuario al interactuar con nuestra aplicación.

De hecho, si en tu aplicación haces algo así:

public static void main(String args[]){
    new MyApp().setVisible(true);
}

entonces el thread main termina inmediatamente después de poner visible el JFrame.  Al hacerlo queda corriendo el thread de Swing, también conocido como EDT (por Event Dispatch Thread). La aplicación continuará corriendo mientras quede ese thread activo independientemente de que el thread main ya esté finalizado.

El tema es que los objetos de Swing no admiten ser manipulados desde otro thread que no sea el EDT, entonces llamar al método setVisible(true) desde el thread main es incorrecto. Para ejecutar código en el thread de Swing tenemos que hacerlo indirectamente:

public static void main(String args[]){
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            new MyApp().setVisible(true);
        }
    });
}

De esa forma el método run() del objeto Runnable que creamos se va a ejecutar en el EDT, tal como se ejecutan los eventos que dispara el usuario.

Hay que estar siempre atentos para evitar manipular objetos de Swing en threads que no sean el EDT, ya sea el thread main o en otro thread que hayamos creado nosotros.