Этот новый ответ использует средство C ++ 11 <chrono>
. В то время как есть другие ответы, которые показывают, как использовать <chrono>
, ни один из них не показывает, как использовать <chrono>
со средством RDTSC
, упомянутым в нескольких других ответах здесь. Поэтому я подумал, что покажу, как использовать RDTSC
с <chrono>
. Кроме того, я покажу, как вы можете шаблонизировать тестовый код на часах, чтобы вы могли быстро переключаться между RDTSC
и встроенными средствами синхронизации вашей системы (которые, вероятно, будут основаны на clock()
, clock_gettime()
и / или QueryPerformanceCounter
.
Обратите внимание, что инструкция RDTSC
специфична для x86. QueryPerformanceCounter
только для Windows. И clock_gettime()
только для POSIX. Ниже я представлю два новых тактовых генератора: std::chrono::high_resolution_clock
и std::chrono::system_clock
, которые, если вы можете предположить C ++ 11, теперь кроссплатформенные.
Во-первых, вот как вы создаете C ++ 11-совместимые часы из инструкции по сборке Intel rdtsc
. Я назову это 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
Все эти часы - подсчитывают циклы ЦП и сохраняют его в беззнаковом 64-разрядном целом числе. Возможно, вам придется настроить синтаксис ассемблера для вашего компилятора. Или ваш компилятор может предложить встроенную функцию, которую вы можете использовать вместо этого (например, now() {return __rdtsc();}
).
Чтобы построить часы, вы должны дать им представление (тип хранилища). Вы также должны указать период времени, который должен быть постоянной времени компиляции, даже если ваша машина может изменять тактовую частоту в разных режимах питания. И из них вы можете легко определить «родную» продолжительность и время ваших часов в терминах этих основ.
Если все, что вы хотите сделать, это вывести количество тактов, то на самом деле не имеет значения, какое число вы даете за период времени. Эта константа вступает в действие только в том случае, если вы хотите преобразовать количество тактов в единицу реального времени, например, в наносекунды. И в этом случае, чем точнее вы сможете указать тактовую частоту, тем точнее будет преобразование в наносекунды (миллисекунды и т. Д.).
Ниже приведен пример кода, который показывает, как использовать x::clock
. На самом деле я шаблонный код на часах, поскольку я хотел бы показать, как вы можете использовать много разных часов с одинаковым синтаксисом. Этот конкретный тест показывает, какова нагрузка на цикл при выполнении того, что вы хотите использовать под циклом:
#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";
}
Первое, что делает этот код, это создает модуль «в реальном времени» для отображения результатов. Я выбрал пикосекунды, но вы можете выбрать любые единицы измерения, которые вам нравятся, на основе целых или с плавающей запятой. В качестве примера можно привести готовый блок std::chrono::nanoseconds
, который я мог бы использовать.
В качестве другого примера я хочу напечатать среднее число тактов на одну итерацию в виде числа с плавающей запятой, поэтому я создаю другую длительность, основанную на double, которая имеет те же единицы измерения, что и такт часов (называемый Cycle
в код).
Цикл рассчитан с вызовами на clock::now()
с обеих сторон. Если вы хотите назвать тип, возвращаемый из этой функции, это:
typename clock::time_point t0 = clock::now();
(как ясно показано в примере x::clock
, и также верно для системных часов).
Чтобы получить длительность в виде тиков с часами с плавающей запятой, нужно просто вычесть два момента времени, а чтобы получить значение для каждой итерации, разделите эту продолжительность на количество итераций.
Вы можете получить счет в любой продолжительности, используя функцию-член count()
. Это возвращает внутреннее представление. Наконец, я использую std::chrono::duration_cast
, чтобы преобразовать длительность Cycle
в длительность picoseconds
и распечатать ее.
Использовать этот код просто:
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>();
}
Выше я выполнил тест с использованием нашего домашнего x::clock
и сравнил эти результаты с использованием двух системных часов: std::chrono::high_resolution_clock
и std::chrono::system_clock
. Для меня это печатает:
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
Это показывает, что у каждого из этих часов есть различный период тактов, так как такты на итерацию сильно различаются для каждого такта. Однако при преобразовании в известную единицу времени (например, в пикосекундах) я получаю примерно одинаковый результат для каждых часов (ваш пробег может отличаться).
Обратите внимание, что мой код полностью свободен от "магических констант преобразования". Действительно, во всем примере есть только два магических числа:
- Тактовая частота моей машины для определения
x::clock
.
- Количество итераций для проверки. Если изменение этого числа приводит к значительным различиям в результатах, то, вероятно, следует увеличить количество итераций или освободить компьютер от конкурирующих процессов во время тестирования.