Esta nueva respuesta usa la <chrono>
facilidad de C ++ 11 . Si bien hay otras respuestas que muestran cómo usar <chrono>
, ninguna muestra cómo usar <chrono>
con la RDTSC
facilidad mencionada en varias de las otras respuestas aquí. Así que pensé que iba a mostrar cómo utilizar RDTSC
con <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 RDTSC
las funciones de reloj integradas de su sistema (que probablemente se basarán en clock()
, clock_gettime()
y / o QueryPerformanceCounter
.
Tenga en cuenta que la RDTSC
instrucción es específica de x86. QueryPerformanceCounter
es solo para Windows. Y clock_gettime()
es solo POSIX. A continuación, presento dos relojes nuevos: std::chrono::high_resolution_clock
y std::chrono::system_clock
, que, si puede asumir C ++ 11, ahora son multiplataforma.
Primero, así es como se crea un reloj compatible con C ++ 11 a partir de las rdtsc
instrucciones 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 deba modificar la sintaxis del lenguaje ensamblador para su 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 puede 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 quiere hacer es generar el número de tics del reloj, realmente no importa qué número dé para el período del reloj. Esta constante solo entra en juego si desea convertir el número de pulsos del reloj en alguna unidad de tiempo real, como nanosegundos. Y en ese caso, cuanto más preciso sea el suministro de la velocidad del reloj, más precisa será la conversión a nanosegundos (milisegundos, lo que sea).
A continuación se muestra un código de ejemplo que muestra cómo usarlo x::clock
. En realidad, he creado una plantilla para el código del reloj, ya que me gustaría mostrar cómo puede usar muchos relojes diferentes con la misma sintaxis exacta. Esta prueba en particular muestra cuál es la sobrecarga de bucle cuando se ejecuta lo que desea cronometrar bajo un bucle:
#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";
}
Lo primero que hace este código es crear una unidad de "tiempo real" para mostrar los resultados. He elegido picosegundos, pero puede elegir cualquier unidad que desee, ya sea integral o basada en punto flotante. Como ejemplo, hay una std::chrono::nanoseconds
unidad prefabricada que podría haber usado.
Como otro ejemplo, quiero imprimir el número promedio de ciclos de reloj por iteración como un punto flotante, así que creo otra duración, basada en el doble, que tiene las mismas unidades que el tic del reloj (llamado Cycle
en el código).
El ciclo se cronometra con llamadas a clock::now()
ambos lados. Si desea nombrar el tipo devuelto por esta función, es:
typename clock::time_point t0 = clock::now();
(como se muestra claramente en el x::clock
ejemplo, y también es cierto para los relojes suministrados por el sistema).
Para obtener una duración en términos de tics de reloj de punto flotante, uno simplemente resta los dos puntos de tiempo, y para obtener el valor por iteración, divida esa duración por el número de iteraciones.
Puede obtener el recuento en cualquier duración utilizando la count()
función miembro. Esto devuelve la representación interna. Finalmente, utilizo std::chrono::duration_cast
para convertir la duración Cycle
a la duración picoseconds
e imprimirla.
Usar este código es 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>();
}
Arriba realizo la prueba usando nuestro hecho en casa x::clock
, y comparo esos resultados con el uso de dos de los relojes provistos por el sistema: std::chrono::high_resolution_clock
y std::chrono::system_clock
. Para mí esto imprime:
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
Esto muestra que cada uno de estos relojes tiene un período de tic diferente, ya que los tics por iteración son muy diferentes para cada reloj. Sin embargo, cuando se convierte a una unidad de tiempo conocida (por ejemplo, picosegundos), obtengo aproximadamente el mismo resultado para cada reloj (su millaje puede variar).
Tenga en cuenta que mi código está completamente libre de "constantes de conversión mágicas". De hecho, solo hay dos números mágicos en todo el ejemplo:
- La velocidad del reloj de mi máquina para definir
x::clock
.
- El número de iteraciones para probar. Si cambiar este número hace que sus resultados varíen mucho, entonces probablemente debería aumentar el número de iteraciones o vaciar su computadora de procesos competidores durante la prueba.