Cómo medir el tiempo transcurrido en Java

Introducción

En este artículo, vamos a analizar cómo medir el tiempo transcurrido en Java. Aunque esto puede parecer sencillo, hay algunas trampas que debemos tener en cuenta. Exploraremos las clases estándar de Java y los paquetes externos que ofrecen funcionalidad para medir el tiempo transcurrido. A través de ejemplos prácticos y explicaciones claras, te ayudaremos a elegir la mejor manera de medir el tiempo en tus proyectos de programación en Java.

1. Overview

Medir el tiempo en programación es una tarea común, especialmente para mejorar el rendimiento de un código o para determinar el tiempo que un algoritmo necesita para ejecutarse. En Java, tenemos varias formas de lograrlo, desde las tradicionales hasta las más modernas introducidas en las versiones recientes.

2. Simple Measurements

2.1. currentTimeMillis()

Cuando encontramos la necesidad de medir el tiempo transcurrido en Java, podríamos intentar hacerlo de la siguiente manera:

long start = System.currentTimeMillis();
// ejecución del código
long finish = System.currentTimeMillis();
long timeElapsed = finish - start;

Si miramos el código, tiene mucho sentido. Obtenemos un timestamp al inicio y otro cuando el código termina. El tiempo transcurrido es la diferencia entre estos dos valores. Sin embargo, el resultado puede ser inexacto ya que System.currentTimeMillis() mide el tiempo del reloj de pared. El tiempo de reloj de pared puede cambiar por muchas razones, como cambiar la hora del sistema, lo que puede afectar los resultados, o el segundo intercalar que interrumpirá el resultado.

2.2. nanoTime()

Otro método en la clase java.lang.System es nanoTime(). Si consultamos la documentación de Java, encontramos la siguiente afirmación:

“Este método solo se puede usar para medir el tiempo transcurrido y no está relacionado con ninguna otra noción de tiempo del sistema o del reloj de pared.”

Utilicémoslo:

long start = System.nanoTime();
// ejecución del código
long finish = System.nanoTime();
long timeElapsed = finish - start;

El código es prácticamente el mismo que antes, con la única diferencia en el método utilizado para obtener los timestamps: nanoTime() en lugar de currentTimeMillis(). nanoTime() devuelve el tiempo en nanosegundos. Por lo tanto, si medimos el tiempo transcurrido en una unidad de tiempo diferente, debemos convertirlo en consecuencia.

Por ejemplo, para convertir a milisegundos, debemos dividir el resultado en nanosegundos por 1.000.000. Sin embargo, otro inconveniente de nanoTime() es que, aunque proporciona una precisión de nanosegundos, no garantiza una resolución de nanosegundos (es decir, con qué frecuencia se actualiza el valor). Aun así, garantiza que la resolución será al menos tan buena como la de currentTimeMillis().

3. Java 8

Si estamos usando Java 8, podemos probar las nuevas clases java.time.Instant y java.time.Duration. Ambas son inmutables, seguras para hilos y utilizan su propia escala de tiempo, la Java Time-Scale, como todas las clases dentro de la nueva API java.time.

3.1. Java Time-Scale

La forma tradicional de medir el tiempo es dividir un día en 24 horas de 60 minutos de 60 segundos, lo que proporciona 86.400 segundos en un día. Sin embargo, los días solares no son siempre igualmente largos. En realidad, la escala de tiempo UTC permite que un día tenga 86.399 o 86.401 segundos SI (segundos internacionales). Un segundo SI es el “segundo estándar internacional” científico que se define mediante períodos de radiación del átomo de cesio 133, lo que es necesario para mantener el día alineado con el Sol.

La Java Time-Scale divide cada día calendario en exactamente 86.400 subdivisiones conocidas como segundos. No hay segundos intercalados.

3.2. Clase Instant

La clase Instant representa un instante en la línea temporal. Básicamente, es un timestamp numérico desde la época estándar de Java, que es 1970-01-01T00:00:00Z.

Para obtener el timestamp actual, podemos usar el método estático Instant.now(). Este método permite pasar un parámetro opcional de tipo Clock. Si se omite, utiliza el reloj del sistema en la zona horaria predeterminada.

Podemos almacenar los tiempos de inicio y final en dos variables, como en los ejemplos anteriores. A continuación, podemos calcular el tiempo transcurrido entre ambos instantes. Para ello, podemos utilizar la clase Duration y su método between() para obtener la duración entre dos objetos Instant. Finalmente, necesitamos convertir Duration a milisegundos:

Instant start = Instant.now();
// ejecución del código        
Instant finish = Instant.now();
long timeElapsed = Duration.between(start, finish).toMillis();

4. StopWatch

Pasando a las bibliotecas, Apache Commons Lang proporciona la clase StopWatch que se puede utilizar para medir el tiempo transcurrido.

4.1. Maven Dependency

Podemos obtener la última versión actualizando el archivo pom.xml:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

La última versión de esta dependencia se puede consultar aquí.

4.2. Medición del tiempo transcurrido con StopWatch

Primero, necesitamos obtener una instancia de la clase y luego simplemente podemos medir el tiempo transcurrido:

StopWatch watch = new StopWatch();
watch.start();

Una vez que tenemos un reloj en funcionamiento, podemos ejecutar el código que deseamos evaluar y luego, al final, simplemente llamamos al método stop(). Finalmente, para obtener el resultado real, llamamos a getTime():

watch.stop();
System.out.println("Tiempo transcurrido: " + watch.getTime()); // Imprime: Tiempo transcurrido: 2501

StopWatch tiene algunos métodos auxiliares adicionales que podemos utilizar para pausar o reanudar nuestra medición. Esto puede ser útil si necesitamos hacer que nuestra evaluación sea más compleja. Sin embargo, señalemos que la clase no es segura para hilos.

5. Conclusión

Existen muchas formas de medir el tiempo en Java. Hemos cubierto una forma muy “tradicional” (y poco precisa) utilizando currentTimeMillis(). Además, revisamos el StopWatch de Apache Commons y observamos las nuevas clases disponibles en Java 8.

En general, para mediciones simples y correctas del tiempo transcurrido, el método nanoTime() es suficiente. También es más corto de escribir que currentTimeMillis(). Sin embargo, para evaluaciones adecuadas, en lugar de medir el tiempo manualmente, podemos utilizar un marco como el Java Microbenchmark Harness (JMH). Este tema va más allá del alcance de este artículo, pero hemos explorado esto aquí.

En resumen, cada método tiene sus ventajas y desventajas dependiendo de las necesidades del proyecto, así que escoge con prudencia el que mejor se adapte a tu situación.