Essa nova resposta usa os recursos do C ++ 11 <chrono>
. Embora existam outras respostas que mostram como usar <chrono>
, nenhuma delas mostra como usar <chrono>
com a RDTSC
facilidade mencionada em várias das outras respostas aqui. Então pensei em mostrar como usar RDTSC
com <chrono>
. Além disso eu vou demonstrar como você pode templatize o código de teste no relógio de modo que você pode alternar rapidamente entre RDTSC
e seu sistema está integrado em instalações de clock (que provavelmente será baseado em clock()
, clock_gettime()
e / ou QueryPerformanceCounter
.
Observe que a RDTSC
instrução é específica para x86. QueryPerformanceCounter
é apenas para Windows. E clock_gettime()
é apenas POSIX. A seguir, apresento dois novos relógios: std::chrono::high_resolution_clock
and std::chrono::system_clock
, que, se você pode assumir o C ++ 11, agora são multiplataforma.
Primeiro, aqui está como você cria um relógio compatível com C ++ 11 a partir das rdtsc
instruções de montagem da Intel . Eu vou chamá-lo 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
Tudo o que esse relógio faz é contar os ciclos da CPU e armazená-los em um inteiro não assinado de 64 bits. Você pode precisar ajustar a sintaxe da linguagem assembly para o seu compilador. Ou seu compilador pode oferecer um intrínseco que você pode usar em seu lugar (por exemplo now() {return __rdtsc();}
).
Para construir um relógio, você deve dar a ele a representação (tipo de armazenamento). Você também deve fornecer o período de clock, que deve ser uma constante de tempo de compilação, mesmo que sua máquina possa alterar a velocidade do clock em diferentes modos de energia. E, a partir deles, você pode definir facilmente a duração e o ponto de tempo "nativo" do seu relógio em termos desses fundamentos.
Se tudo o que você deseja fazer é mostrar o número de tiques do relógio, realmente não importa o número fornecido para o período do relógio. Essa constante só entra em ação se você quiser converter o número de tiques do relógio em alguma unidade de tempo real, como nanossegundos. E, nesse caso, quanto mais preciso você for capaz de fornecer a velocidade do clock, mais precisa será a conversão em nanossegundos (milissegundos, o que for).
Abaixo está o código de exemplo que mostra como usar x::clock
. Na verdade, criei um modelo para o código do relógio, pois gostaria de mostrar como você pode usar muitos relógios diferentes com a mesma sintaxe exata. Este teste específico está mostrando qual é a sobrecarga do loop ao executar o que você deseja cronometrar em um 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";
}
A primeira coisa que esse código faz é criar uma unidade de "tempo real" para exibir os resultados. Eu escolhi picossegundos, mas você pode escolher qualquer unidade que desejar, seja integral ou baseada em ponto flutuante. Como exemplo, há uma std::chrono::nanoseconds
unidade pré-fabricada que eu poderia ter usado.
Como outro exemplo, quero imprimir o número médio de ciclos de clock por iteração como um ponto flutuante, então crio outra duração, com base em double, que tem as mesmas unidades que o tique do relógio (chamado Cycle
no código).
O loop é cronometrado com chamadas para clock::now()
ambos os lados. Se você deseja nomear o tipo retornado por esta função, é:
typename clock::time_point t0 = clock::now();
(como mostrado claramente no x::clock
exemplo, e também é verdadeiro para os relógios fornecidos pelo sistema).
Para obter uma duração em termos de tiques do relógio de ponto flutuante, basta subtrair os dois pontos no tempo e, para obter o valor por iteração, divida essa duração pelo número de iterações.
Você pode obter a contagem em qualquer duração usando a count()
função de membro. Isso retorna a representação interna. Finalmente, costumo std::chrono::duration_cast
converter a duração Cycle
em duração picoseconds
e imprimi-la.
Usar este código é simples:
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>();
}
Acima, eu exercito o teste usando nosso feito em casa x::clock
e comparo esses resultados com o uso de dois relógios fornecidos pelo sistema: std::chrono::high_resolution_clock
e std::chrono::system_clock
. Para mim, isso 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
Isso mostra que cada um desses relógios tem um período de tique diferente, já que os tiques por iteração são muito diferentes para cada relógio. No entanto, quando convertido para uma unidade de tempo conhecida (por exemplo, picossegundos), obtenho aproximadamente o mesmo resultado para cada relógio (sua milhagem pode variar).
Observe como meu código está completamente livre de "constantes de conversão mágicas". Na verdade, existem apenas dois números mágicos em todo o exemplo:
- A velocidade do relógio da minha máquina para definir
x::clock
.
- O número de iterações a serem testadas. Se a alteração desse número fizer seus resultados variarem muito, você provavelmente deve aumentar o número de iterações ou esvaziar o computador de processos concorrentes durante o teste.