I’m back! And, Simple Timer

Español

A considerable amount of time has passed, but I’m back, hopefully trying to be more consistent in the frequency and quantity of post, but I can’t ensure anything 🙂

Today I bring you, a very simple timer written in C++11 using the chrono library. It’s implemented in a single header, using a bit of templates. With nothing else to add, let’s dissect it:

#ifndef _TIMER_H_
#define _TIMER_H_
#include <chrono>

namespace util {
    template<typename time_ratio_t>
    class Timer {

Nothing much to say from this part, just ifndef guards and namespace & class definition. Note the typename time_ratio_t, this type will define how the timer will keep track of time (more on this later).

    private:
        using clock = std::chrono::high_resolution_clock;
        using point = std::chrono::time_point<clock>;
        const point m_start = clock::now();
        point m_snap = m_start;

        template<typename count_t>
        static count_t get_diff(const point& start, const point& end) {
            using duration_t = std::chrono::duration<count_t, time_ratio_t>;
            return std::chrono::duration_cast<duration_t>(end - start).count();
        }

This is more interesting, first a couple of private using declarations, to shorten the names of a couple of types, the high_resolution_clock is the clock we’ll use, there are other available, with different properties, you can read more of them here: High Resolution Clock.

We will implement the functionalities of the timer using 2 member variables, both of them “time point”, the C++ / std::chrono version of saying “instances in time”: the start, being also created when the timer is created, and another one to keep track of snapshots.

Finally, there’s the get_diff() function, that returns the difference between a starting point and an end point.

  • This function is made static only because it doesn’t interacts with the members of the timer.
  • The template parameter defines how the return value will be represented (i.e. float, uint64_t, etc.).
  • The using declaration is there only to shorten the next statement.
  • The return value it’s the result of using the count() function that calculates the amount of units of time between the 2 instances of time, each tick expressed with the size defined with the template class parameter (seconds, milliseconds, etc.).

A std::duration type, is std::chrono way of representing well, a duration, a difference between two points in time. It’s composed of 2 underlying types, one for the representation and one for its ratio with respect to 1 second. This is where the template parameters of the Timer comes into place. This timer can be created to track seconds, milliseconds or any ratio, passing any of the standard ratios defined by the stl (like std::milli) which is really handy.

And then the function can return that quantity of time on the count_t representation. (Be wary of overflows with very big values for the amount of time not fitting in the representation, the Timer doesn’t do this)

Moving on:

    public:
        void snap() {
            m_snap = clock::now();
        }

        template<typename count_t>
        count_t get_delta() const {
            return get_diff<count_t>(m_snap, clock::now());
        }

        template<typename count_t>
        count_t get_total() const {
            return get_diff<count_t>(m_start, clock::now());
        }

This part is easier, building on top of the private members and the static function provides 3 public member functions,

  • snap() which takes a time snapshot used to calculate time deltas, using the m_snap member.
  • get_delta() which returns the delta (as a count_t representation of the duration) between the last time snap() was called and the current time.
  • get_total() that returns total time of existence of the timer (that’s why it uses m_start).

And that’s it, at least for this implementation…

Things to try 🙂

  • Templatize the clock, so that the timer works on any of the std::chrono clocks availables.
  • Generalize the m_start and m_end members to a list, so that it’s possible to have arbitrary different measurements of time, or even, the same measurements represented using different data types.
  • Add SFINAE-based controls to prevent the compilation of code that creates potential data loss when get_* functions are called with a “not big enough” representation to hold the duration count. Think of the return type of std::chrono::duration::count() and how it relates to the count_t template parameter and, for example, std::enable_if.
  • Other things 😛 I’m out of ideas for this one

Hope you like it, here you can find the source code for the timer and an test implementation to show how it can be used.

Back to Top



Una considerable cantidad de tiempo pasó, pero estoy de vuelta. Tratando de ser más consistente conla frecuencia y la cantidad de publicaciones, pero sin prometer nada 🙂

Hoy tengo un timer muy simple escrito en C++11 y usando la librería chrono. Está implementado como un solo header, usando un poco de templates. Sin nada más que agregar, acá está:

#ifndef _TIMER_H_
#define _TIMER_H_
#include <chrono>

namespace util {
    template<typename time_ratio_t>
    class Timer {

En esta parte no hay mucho que decir, solo guardas ifndef y la definición de namespace & class. Hay que notar el typename time_ratio_t, este tipo va a definir cómo es que el timer va a seguir el paso del tiempo (más sobre esto luego).

    private:
        using clock = std::chrono::high_resolution_clock;
        using point = std::chrono::time_point<clock>;
        const point m_start = clock::now();
        point m_snap = m_start;

        template<typename count_t>
        static count_t get_diff(const point& start, const point& end) {
            using duration_t = std::chrono::duration<count_t, time_ratio_t>;
            return std::chrono::duration_cast<duration_t>(end - start).count();
        }

Esta parte es más interesante, primero un par de declaraciones using privadas, para acortar el nombre de algunos tipos, el high_resolution_clock es el reloj interno que va a usar el timer, se pueden utilizar otros que tienen diferentes propiedades, podés leer más sobre ello acá: High Resolution Clock.

Las funcionalidades van a quedar implementadas usando 2 variables miembro, ambas “time point”, que es la forma de decir “instancia en el tiempo” que tiene C++ / std::chrono: la variable de comienzo, creada e instanciada al mismo tiempo que el timer y otra para mantener guardada la última instancia obtenida con un snapshot.

Finalmente la función get_diff(), que retorna la diferencia entre dos puntos en el tiempo.

  • Esta función esta marcada como static porque no tiene interacción con ningún miembro del objeto timer.
  • El parámetro del template define la representación del valor de retorno (p.e. float, uint64_t, etc.).
  • La declaración de using está solamente para acortar un poco la siguiente línea.
  • El valor de retorno es el resultado de utilizar la función count() que calcula la cantidad de unidades de tiempo que pasaron entre los dos instantes, cada tick expresado en el tamaño definido con el parametro template de la clase (segundos, milisegundos, etc.).

Un tipo std::duration, es la manera de std::chrono de representar bueno, una duración, la diferencia entre dos puntos en el tiempo. Está compuesto por 2 tipos, una para la represetación del acumulador del tiempo y otro para indicar cómo se debe interpretar cada unidad del acumulador, el ratio que tiene con respecto a 1 segundo. Ahí es donde juega el parámetro template de la clase. Este timer puede ser creado para contar segundos, milisegundos, o cualquier otro ratio, pasando los tipos estándar definidos en la stl (como std::milli) lo cual lo hace útil.

A raíz de eso, la función puede retornar la cantidad de tiempo que pasó utilizando el tipo count_t. (Guarda con perder información, ya que no hay ningun control que satisfaga que count_t es lo suficientemente grande para retornar la diferencia de tiempo utilizando el ratio y representación del Timer)

Siguiendo:

    public:
        void snap() {
            m_snap = clock::now();
        }

        template<typename count_t>
        count_t get_delta() const {
            return get_diff<count_t>(m_snap, clock::now());
        }

        template<typename count_t>
        count_t get_total() const {
            return get_diff<count_t>(m_start, clock::now());
        }

Esta parte es más fácil, construyendo sobre los miembros privados y la función estática, la clase provee 3 métodos públicos:

  • snap() que almacena una nueva instancia de tiempo, para calcular el delta, utilizando el miembro m_snap member.
  • get_delta() que retorna el delta (como una representación de duración expresada con el tipo count_t) entre la última vez que se llamó a snap() y el tiempo actual.
  • get_total() que retorna el tiempo total de existencia del timer (por ello utiliza m_start).

Y ya está, por lo menos para esta implementación…

Algunas cosas para probar 🙂

  • Convertir el tipo de reloj en un parámetro de template, para que el timer sirva con cualquiera de los relojes disponibles en std::chrono.
  • Generalizar los miembros m_start y m_end a una lista, así es posbile tener medidas de tiempo arbitrarias, e incluso poder retornar las mismas medidas utilizando diferentes timpos de datos.
  • Agregar controles basados en SFINAE* para prevenir la compilación de código que pueda crear potenciales pérdidas de datos al llamar a las funciones get_* utilizando un tipo “no lo suficientemente grande” para representar el valor de cantidad de la duración. Pesá en el valor de retorno de la función std::chrono::duration::count() y cómo se relaciona con el parámetro de template count_t y, por ejemplo std::enable_if.
  • Alguna otra cosa 😛 No tengo más ideas

Espero haya gustado, acá se puede encontrar el código fuente del timer y una implementación de test para mostrar cómo se puede usar.

*Perdón por el link en Inglés, en lo breve que busqué no encontré una buena fuente que explicara SFINAE en español. Capaz hasta puedo escribir algo luego 🙂
Inicio

Advertisements

Leave a Reply (Deja una respuesta)

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s