Этот новый ответ использует возможности 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, из rdtsc
инструкции по сборке Intel . Я назову это 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
.
- Количество итераций для проверки. Если изменение этого числа приводит к тому, что ваши результаты сильно различаются, вам, вероятно, следует увеличить количество итераций или освободить свой компьютер от конкурирующих процессов во время тестирования.