Ta nowa odpowiedź wykorzystuje funkcję C ++ 11 <chrono>
. Chociaż istnieją inne odpowiedzi, które pokazują, jak używać <chrono>
, żadna z nich nie pokazuje, jak korzystać <chrono>
z funkcji RDTSC
wspomnianej w kilku innych odpowiedziach tutaj. Więc pomyślałem, że pokażę jak korzystać RDTSC
z <chrono>
. Dodatkowo będę pokazują, jak można templatize kod testowy na zegarze, dzięki czemu można szybko przełączać się między RDTSC
a system wbudowany w obiektach zegar (który prawdopodobnie zostanie na podstawie clock()
, clock_gettime()
i / lub QueryPerformanceCounter
.
Zwróć uwagę, że RDTSC
instrukcja jest specyficzna dla x86. QueryPerformanceCounter
dotyczy tylko systemu Windows. I clock_gettime()
jest tylko POSIX. Poniżej przedstawiam dwa nowe zegary: std::chrono::high_resolution_clock
i std::chrono::system_clock
, które, jeśli można założyć C ++ 11, są teraz wieloplatformowe.
Po pierwsze, oto jak utworzyć zegar zgodny z C ++ 11 na podstawie rdtsc
instrukcji montażu Intela . Nazwę to 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
Wszystko, co robi ten zegar, to zliczanie cykli procesora i przechowywanie ich w 64-bitowej liczbie całkowitej bez znaku. Być może będziesz musiał dostosować składnię języka asemblera dla swojego kompilatora. Lub Twój kompilator może oferować funkcję wewnętrzną, której możesz użyć zamiast tego (np now() {return __rdtsc();}
.).
Aby zbudować zegar, musisz nadać mu reprezentację (typ pamięci). Należy również podać okres zegara, który musi być stałą czasową kompilacji, nawet jeśli urządzenie może zmieniać częstotliwość zegara w różnych trybach zasilania. Na ich podstawie można łatwo zdefiniować „natywny” czas trwania i punkt czasowy swojego zegara, korzystając z tych podstaw.
Jeśli wszystko, co chcesz zrobić, to wyprowadzić liczbę taktów zegara, tak naprawdę nie ma znaczenia, jaką liczbę podasz dla okresu zegara. Ta stała pojawia się tylko wtedy, gdy chcesz przeliczyć liczbę tyknięć zegara na jakąś jednostkę czasu rzeczywistego, taką jak nanosekundy. W takim przypadku, im dokładniej jesteś w stanie podać częstotliwość zegara, tym dokładniejsza będzie konwersja na nanosekundy (milisekundy, cokolwiek).
Poniżej znajduje się przykładowy kod, który pokazuje, jak używać x::clock
. Właściwie utworzyłem szablon kodu na zegarze, ponieważ chciałbym pokazać, jak można używać wielu różnych zegarów z dokładnie tą samą składnią. Ten konkretny test pokazuje, jakie jest obciążenie pętli podczas uruchamiania tego, co chcesz, w pętli:
#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";
}
Pierwszą rzeczą, jaką robi ten kod, jest utworzenie jednostki „czasu rzeczywistego” do wyświetlania wyników. Wybrałem pikosekundy, ale możesz wybrać dowolne jednostki, oparte na liczbach całkowitych lub zmiennoprzecinkowych. Jako przykład std::chrono::nanoseconds
mogę użyć gotowej jednostki.
Jako inny przykład chcę wydrukować średnią liczbę cykli zegara na iterację jako zmiennoprzecinkową, więc tworzę inny czas trwania, oparty na liczbie podwójnej, który ma te same jednostki, co takt zegara (nazywany Cycle
w kodzie).
Pętla jest synchronizowana z wywołaniami clock::now()
po obu stronach. Jeśli chcesz nazwać typ zwracany przez tę funkcję, jest to:
typename clock::time_point t0 = clock::now();
(jak wyraźnie widać w x::clock
przykładzie, dotyczy to również zegarów dostarczanych przez system).
Aby uzyskać czas trwania w postaci taktów zegara zmiennoprzecinkowego, wystarczy odjąć dwa punkty czasowe, a aby uzyskać wartość na każdą iterację, podzielić ten czas trwania przez liczbę iteracji.
Możesz uzyskać liczbę w dowolnym czasie za pomocą count()
funkcji członkowskiej. Zwraca wewnętrzną reprezentację. Na koniec używam, std::chrono::duration_cast
aby przekonwertować czas trwania Cycle
na czas trwania picoseconds
i wydrukować to.
Korzystanie z tego kodu jest proste:
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>();
}
Powyżej wykonuję test na naszej domowej roboty x::clock
i porównuję te wyniki z dwoma zegarami dostarczonymi przez system: std::chrono::high_resolution_clock
i std::chrono::system_clock
. Dla mnie to drukuje:
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
Pokazuje to, że każdy z tych zegarów ma inny okres taktowania, ponieważ tiki na iterację są bardzo różne dla każdego zegara. Jednak po przeliczeniu na znaną jednostkę czasu (np. Pikosekundy), otrzymuję w przybliżeniu ten sam wynik dla każdego zegara (Twój przebieg może się różnić).
Zwróć uwagę, że mój kod jest całkowicie wolny od „magicznych stałych konwersji”. Rzeczywiście, w całym przykładzie są tylko dwie magiczne liczby:
- Szybkość zegara mojej maszyny w celu zdefiniowania
x::clock
.
- Liczba iteracji do przetestowania. Jeśli zmiana tej liczby powoduje znaczne różnice w wynikach, prawdopodobnie należy zwiększyć liczbę iteracji lub opróżnić komputer z konkurencyjnych procesów podczas testowania.