Función de temporizador para proporcionar tiempo en nanosegundos usando C++

Resuelto gagneet asked hace 16 años • 17 respuestas

Deseo calcular el tiempo que tardó una API en devolver un valor. El tiempo necesario para tal acción es del orden de nanosegundos. Como la API es una clase/función de C++, estoy usando timer.h para calcular lo mismo:

  #include <ctime>
  #include <iostream>

  using namespace std;

  int main(int argc, char** argv) {

      clock_t start;
      double diff;
      start = clock();
      diff = ( std::clock() - start ) / (double)CLOCKS_PER_SEC;
      cout<<"printf: "<< diff <<'\n';

      return 0;
  }

El código anterior proporciona el tiempo en segundos. ¿Cómo consigo lo mismo en nanosegundos y con más precisión?

gagneet avatar Nov 09 '08 01:11 gagneet
Aceptado

Lo que otros han publicado sobre ejecutar la función repetidamente en un bucle es correcto.

Para Linux (y BSD), desea utilizar clock_gettime() .

#include <sys/time.h>

int main()
{
   timespec ts;
   // clock_gettime(CLOCK_MONOTONIC, &ts); // Works on FreeBSD
   clock_gettime(CLOCK_REALTIME, &ts); // Works on Linux
}

Para Windows, desea utilizar QueryPerformanceCounter . Y aquí hay más información sobre QPC.

Aparentemente hay un problema conocido con QPC en algunos conjuntos de chips, por lo que es posible que desees asegurarte de no tener esos conjuntos de chips. Además, algunos AMD de doble núcleo también pueden causar problemas . Vea la segunda publicación de sebbbi, donde afirma:

QueryPerformanceCounter() y QueryPerformanceFrequency() ofrecen una resolución un poco mejor, pero tienen problemas diferentes. Por ejemplo, en Windows XP, todas las CPU AMD Athlon X2 de doble núcleo devuelven la PC de cualquiera de los núcleos "al azar" (la PC a veces salta un poco hacia atrás), a menos que instale especialmente el paquete de controladores AMD de doble núcleo para solucionar el problema. No hemos notado que ninguna otra CPU de doble núcleo tenga problemas similares (p4 dual, p4 ht, core2 dual, core2 quad, phenom quad).

EDITAR 16/07/2013:

Parece que existe cierta controversia sobre la eficacia de QPC en determinadas circunstancias, como se indica en http://msdn.microsoft.com/en-us/library/windows/desktop/ee417693(v=vs.85).aspx

...Si bien QueryPerformanceCounter y QueryPerformanceFrequency generalmente se ajustan para múltiples procesadores, los errores en el BIOS o los controladores pueden provocar que estas rutinas devuelvan valores diferentes a medida que el subproceso se mueve de un procesador a otro...

Sin embargo, esta respuesta de StackOverflow https://stackoverflow.com/a/4588605/34329 indica que QPC debería funcionar bien en cualquier sistema operativo MS después del service pack 2 de Win XP.

Este artículo muestra que Windows 7 puede determinar si los procesadores tienen un TSC invariante y recurrir a un temporizador externo si no lo tienen. http:// Performancebydesign.blogspot.com/2012/03/high-solving-clocks-and-timers-for.html La sincronización entre procesadores sigue siendo un problema.

Otra buena lectura relacionada con los temporizadores:

  • https://blogs.oracle.com/dholmes/entry/inside_the_hotspot_vm_clocks
  • http://lwn.net/Articles/209101/
  • http://rendimientobydesign.blogspot.com/2012/03/high-solving-clocks-and-timers-for.html
  • ¿Estado del contador de rendimiento de consulta?

Vea los comentarios para más detalles.

grieve avatar Nov 08 '2008 21:11 grieve

Esta nueva respuesta utiliza la función de C++ 11 <chrono>. Si bien hay otras respuestas que muestran cómo usar <chrono>, ninguna de ellas muestra cómo usar <chrono>la RDTSCfunción mencionada en varias de las otras respuestas aquí. Entonces pensé en mostrar cómo usarlo RDTSCcon <chrono>. Además, demostraré cómo puede crear una plantilla del código de prueba en el reloj para que pueda cambiar rápidamente entre RDTSClas funciones de reloj integradas de su sistema (que probablemente estarán basadas en clock(), clock_gettime()y/o QueryPerformanceCounter.

Tenga en cuenta que la RDTSCinstrucción es específica de x86. QueryPerformanceCounteres solo Windows. Y clock_gettime()es solo POSIX. A continuación presento dos nuevos relojes: std::chrono::high_resolution_clocky std::chrono::system_clock, que, si puedes asumir C++ 11, ahora son multiplataforma.

Primero, así es como se crea un reloj compatible con C++11 a partir de las rdtscinstrucciones de ensamblaje de Intel. Lo llamaré x::clock:

#include <chrono>

namespace x
{

struct clock
{
    typedef unsigned long long                 rep;
    typedef std::ratio<1, 2'800'000'000>       period; // My machine is 2.8 GHz
    typedef std::chrono::duration<rep, period> duration;
    typedef std::chrono::time_point<clock>     time_point;
    static const bool is_steady =              true;

    static time_point now() noexcept
    {
        unsigned lo, hi;
        asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
        return time_point(duration(static_cast<rep>(hi) << 32 | lo));
    }
};

}  // x

Todo lo que hace este reloj es contar los ciclos de la CPU y almacenarlos en un entero de 64 bits sin signo. Es posible que necesites modificar la sintaxis del lenguaje ensamblador para tu compilador. O su compilador puede ofrecer un intrínseco que puede usar en su lugar (por ejemplo now() {return __rdtsc();}).

Para construir un reloj hay que darle la representación (tipo de almacenamiento). También debe proporcionar el período del reloj, que debe ser una constante de tiempo de compilación, aunque su máquina pueda cambiar la velocidad del reloj en diferentes modos de energía. Y a partir de ellos, puede definir fácilmente la duración y el punto de tiempo "nativos" de su reloj en términos de estos fundamentos.

Si todo lo que desea hacer es generar el número de tics del reloj, realmente no importa qué número proporcione para el período del reloj. Esta constante solo entra en juego si desea convertir el número de tics del reloj en alguna unidad de tiempo real, como nanosegundos. Y en ese caso, cuanto más precisa pueda proporcionar la velocidad del reloj, más precisa será la conversión a nanosegundos (milisegundos, lo que sea).

Below is example code which shows how to use x::clock. Actually I've templated the code on the clock as I'd like to show how you can use many different clocks with the exact same syntax. This particular test is showing what the looping overhead is when running what you want to time under a loop:

#include <iostream>

template <class clock>
void
test_empty_loop()
{
    // Define real time units
    typedef std::chrono::duration<unsigned long long, std::pico> picoseconds;
    // or:
    // typedef std::chrono::nanoseconds nanoseconds;
    // Define double-based unit of clock tick
    typedef std::chrono::duration<double, typename clock::period> Cycle;
    using std::chrono::duration_cast;
    const int N = 100000000;
    // Do it
    auto t0 = clock::now();
    for (int j = 0; j < N; ++j)
        asm volatile("");
    auto t1 = clock::now();
    // Get the clock ticks per iteration
    auto ticks_per_iter = Cycle(t1-t0)/N;
    std::cout << ticks_per_iter.count() << " clock ticks per iteration\n";
    // Convert to real time units
    std::cout << duration_cast<picoseconds>(ticks_per_iter).count()
              << "ps per iteration\n";
}

The first thing this code does is create a "real time" unit to display the results in. I've chosen picoseconds, but you can choose any units you like, either integral or floating point based. As an example there is a pre-made std::chrono::nanoseconds unit I could have used.

As another example I want to print out the average number of clock cycles per iteration as a floating point, so I create another duration, based on double, that has the same units as the clock's tick does (called Cycle in the code).

The loop is timed with calls to clock::now() on either side. If you want to name the type returned from this function it is:

typename clock::time_point t0 = clock::now();

(as clearly shown in the x::clock example, and is also true of the system-supplied clocks).

To get a duration in terms of floating point clock ticks one merely subtracts the two time points, and to get the per iteration value, divide that duration by the number of iterations.

You can get the count in any duration by using the count() member function. This returns the internal representation. Finally I use std::chrono::duration_cast to convert the duration Cycle to the duration picoseconds and print that out.

To use this code is simple:

int main()
{
    std::cout << "\nUsing rdtsc:\n";
    test_empty_loop<x::clock>();

    std::cout << "\nUsing std::chrono::high_resolution_clock:\n";
    test_empty_loop<std::chrono::high_resolution_clock>();

    std::cout << "\nUsing std::chrono::system_clock:\n";
    test_empty_loop<std::chrono::system_clock>();
}

Above I exercise the test using our home-made x::clock, and compare those results with using two of the system-supplied clocks: std::chrono::high_resolution_clock and std::chrono::system_clock. For me this prints out:

Using rdtsc:
1.72632 clock ticks per iteration
616ps per iteration

Using std::chrono::high_resolution_clock:
0.620105 clock ticks per iteration
620ps per iteration

Using std::chrono::system_clock:
0.00062457 clock ticks per iteration
624ps per iteration

This shows that each of these clocks has a different tick period, as the ticks per iteration is vastly different for each clock. However when converted to a known unit of time (e.g. picoseconds), I get approximately the same result for each clock (your mileage may vary).

Note how my code is completely free of "magic conversion constants". Indeed, there are only two magic numbers in the entire example:

  1. The clock speed of my machine in order to define x::clock.
  2. The number of iterations to test over. If changing this number makes your results vary greatly, then you should probably make the number of iterations higher, or empty your computer of competing processes while testing.
Howard Hinnant avatar Jul 14 '2012 16:07 Howard Hinnant