Dominando el tiempo con std::chrono en C++

std::chrono es una biblioteca de tipos para la manipulación del tiempo que evita los errores clásicos de la programación en C de usar enteros desnudos para representar segundos, milisegundos o microsegundos. El sistema se basa en tres conceptos fundamentales: las duraciones (std::chrono::duration), que representan una cantidad de tiempo (el “cuánto”); los puntos en el tiempo (std::chrono::time_point), que representan un momento específico (el “cuándo”); y los relojes (std::chrono::clock), que proporcionan la referencia para los puntos en el tiempo.

La lógica interna se apoya en la metaprogramación de plantillas y std::ratio. Una duration es esencialmente una pareja de un valor numérico (Rep) y un periodo (Period), donde el periodo es una fracción que define la unidad. Esto permite que el compilador garantice la seguridad de tipos: no puedes sumar accidentalmente una variable que representa “segundos” a una que representa “nanosegundos” sin una conversión explícita. El diseño busca la abstracción de coste cero: en tiempo de ejecución, una duration es simplemente un tipo entero optimizado, pero en tiempo de compilación, el sistema de tipos es extremadamente estricto.

Debes usar std::chrono::steady_clock siempre que necesites medir intervalos de tiempo (como el tiempo de ejecución de una función), ya que es monotónico, es decir, su valor nunca retrocede. Si necesitas registrar la hora actual para un log o una marca de tiempo que coincida con el reloj del sistema (el “tiempo de pared”), utiliza std::chrono::system_clock. Si tu hardware lo permite, std::chrono::high_resolution_clock te ofrece la mayor precisión disponible.

Si intentas convertir una unidad de alta precisión a una de baja precisión de forma implícita (por ejemplo, convertir milliseconds a seconds), el compilador lanzará un error. Esto es una medida de seguridad para evitar la pérdida silenciosa de información. Asimismo, si usas system_clock para medir la duración de un proceso y el sistema ajusta la hora mediante NTP durante la ejecución, tu medición podría resultar en un tiempo negativo o un salto errático.

#include <iostream>
#include <chrono>
#include <thread>
#include <vector>

// C++14 introduce los literales de duración como 1s, 500ms, etc.
using namespace std::chrono_literals;

void proceso_pesado() {
    // Simulamos una tarea que tarda algo de tiempo
    std::this_thread::sleep_for(1500ms);
}

void ejecutar_benchmark() {
    // Usamos steady_clock para medir intervalos (es monotónico)
    const auto start = std::chrono::steady_clock::now();

    proceso_pesado();

    const auto end = std::chrono::steady_clock::now();

    // La resta de dos time_point de un mismo reloj resulta en una duration
    const std::chrono::duration<double> elapsed = end - start;

    std::cout << "Tiempo de ejecución (segundos): " << elapsed.count() << "s\n";
}

int main() {
    // Mostrar la hora actual del sistema (tiempo de pared)
    // system_clock puede saltar si el usuario cambia la hora del SO
    const auto now = std::chrono::system_clock::now();
    const auto now_c = std::chrono::system_clock::to_time_t(now);
    std::cout << "Hora actual (timestamp): " << now_c << "\n";

    ejecutar_benchmark();

    // Ejemplo de conversión explícita usando duration_cast
    // No podemos convertir implícitamente de ms a s porque habría pérdida
    std::chrono::milliseconds ms_dur = 2500ms;
    auto s_dur = std::chrono::duration_cast<std::chrono::seconds>(ms_dur);
    
    std::cout << "Conversión de 2500ms a segundos: " << s_dur.count() << "s\n";

    // C++20 introduce nuevas unidades como días o semanas
    std::chrono::days d{7};
    std::cout << "Una semana tiene " << d.count() << " días.\n";

    return 0;
}

Análisis del código

En ejecutar_benchmark, capturamos el estado del reloj con std::chrono::steady_clock::now(), lo que nos devuelve un std::chrono::time_point. Al restar end - start, el compilador realiza una operación de resta sobre los valores internos del time_point, resultando en una std::chrono::duration. Al asignar este resultado a std::chrono::duration<double>, estamos aprovechando que la duración predeterminada de chrono usa segundos; el compilador realiza la conversión automática a double para mantener la precisión.

En la función main, std::chrono::system_clock::now() nos da el tiempo de pared. A diferencia de steady_clock, este punto en el tiempo puede verse afectado por ajustes del sistema operativo. Usamos std::chrono::duration_cast<std::chrono::seconds>(ms_dur) para realizar una conversión de una unidad más pequeña (milliseconds) a una más grande (seconds). Esta operación es necesaria porque, de lo contrario, el compilador detectaría una posible pérdida de precisión y detendría la compilación.

El uso de std::chrono_literals permite escribir 1500ms o 2s en lugar de construir objetos de forma verbosa. Esto no es solo azúcar sintáctica; es la forma de asegurar que el tipo de la constante sea el correcto desde el inicio, permitiendo que las reglas de conversión de std::chrono actúen de forma óptima.

El error frecuente

El error más común es intentar una conversión de “pérdida de precisión” (narrowing conversion) de forma implícita.

std::chrono::milliseconds ms = 1500ms;
// ERROR DE COMPILACIÓN: el compilador no permite perder información silenciosamente
std::chrono::seconds s = ms; 

// FORMA CORRECTA: usar duration_cast para dejar claro que aceptamos la pérdida
std::chrono::seconds s_correcto = std::chrono::duration_cast<std::chrono::seconds>(ms);

Si intentas compilar el primer bloque, el compilador (gcc o clang con -Wall -Wextra) te indicará que no puede convertir duration<long, ratio<1, 1000>> a duration<long, ratio<1, 1>> porque la conversión es destructiva. Para depurar esto, si el error ocurre en una parte compleja de la lógica, el uso de static_assert con std::is_same_v puede ayudarte a verificar qué tipos de duración estás manejando realmente en tiempo de compilación.

93

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio