Вплоть до версии C++11 не существовало стандартного способа достаточно точного измерения времени, в течение которого выполняется тот или другой участок кода на C++. Программист был вынужден использовать сторонние библиотеки, такие как Boost или POCO, или использовать непереносимые методы работы со временем, предлагаемые конкретной операционной системой. С появлением C++11 ситуация сильно изменилась в лучшую сторону.
Заголовочный файл <chrono>
из стандартной библиотеки предоставляет три варианта таймеров:
- system_clock — таймер реального времени, используемый операционной системой;
- high_resolution_clock — таймер с минимально возможным периодом, который только доступен на конкретной ОС;
- steady_clock — монотонный таймер, который гарантированно не может быть изменен никаким образом.
Для замеров производительности участков кода нам более всего подойдет третий вариант — steady_clock
, поскольку этот таймер не может быть изменен системой, что гарантирует, что разница между двумя последовательными моментами времени всегда будет положительной.
В дополнение к этому стандартная библиотека предоставляет набор функций, позволяющих сделать некоторые проверки касательно точности таймеров и действительно ли конкретный таймер является монотонным и неизменяемым. Поскольку реализация конкретного таймера является платформозависимой, перед его использованием никогда не помешает сделать некоторые проверки:
#include <chrono> #include <iostream> int main(int argc, char *argv[]) { using namespace std; using namespace std::chrono; cout << "system_clock:" << endl; cout << " numerator: " << system_clock::period::num << endl; cout << " denominator: " << system_clock::period::den << endl; cout << " is_steady: " << boolalpha << system_clock::is_steady << endl; cout << endl; cout << "high_resolution_clock:" << endl; cout << " numerator: " << high_resolution_clock::period::num << endl; cout << " denominator: " << high_resolution_clock::period::den << endl; cout << " is_steady: " << boolalpha << high_resolution_clock::is_steady << endl; cout << endl; cout << "steady_clock:" << endl; cout << " numerator: " << steady_clock::period::num << endl; cout << " denominator: " << steady_clock::period::den << endl; cout << " is_steady: " << boolalpha << steady_clock::is_steady << endl; return 0; }
Результат запуска этого кода, скомпилированного gcc-5.4 на Linux:
system_clock: numerator: 1 denominator: 1000000000 is_steady: false high_resolution_clock: numerator: 1 denominator: 1000000000 is_steady: false steady_clock: numerator: 1 denominator: 1000000000 is_steady: true
Как видим, на этой платформе все таймеры имеют точность в наносекундах, и steady_clock
является монотонным.
То же самое на clang-4.2 и MacOS:
system_clock: numerator: 1 denominator: 1000000 is_steady: false high_resolution_clock: numerator: 1 denominator: 1000000000 is_steady: true steady_clock: numerator: 1 denominator: 1000000000 is_steady: true
А здесь ситуация немного другая — только system_clock
не будет гарантировать нам правильных измерений, к тому же имеет меньшую точность.
Чтобы произвести нужные нам замеры для определения времени, потраченного на выполнение какого-либо участка кода, можно сделать примерно так, как показано ниже:
#include <chrono> #include <iostream> int main(int argc, char *argv[]) { using namespace std; using namespace std::chrono; steady_clock::time_point start = steady_clock::now(); //Здесь начинается измеряемый участок кода for(u_int32_t i=0; i<0xFFFFFFFF; i++) { u_int32_t j = i; } //Здесь заканчивается измеряемый участок кода steady_clock::time_point end = steady_clock::now(); steady_clock::duration dur = end - start; cout << "timing in milliseconds: " << duration<double, milli>(dur).count() << " ms" << endl; cout << "timing in nanoseconds: " << duration<double, nano>(dur).count() << " ns" << endl; return 0; }
Как видно, нужно взять текущее время на начало и конец прохождения тестирукмого участка и найти разницу между ними. Результат можно представить в единицах, которые наиболее удобны для каждого случая. Вывод нашей скомпилированной программы в милисекундах и наносекундах:
timing in milliseconds: 0.000255 ms timing in nanoseconds: 255 ns
В подобных ситуациях, конечно же, всегда хочется сделать как можно более качественный инструмент, чтобы его использование не привносило слишком больших усилий и неудобств. И с таким инструментом в виде специального небольшого класса можно ознакомиться в статье на сайте QuantPro.ru. Насколько удобно им пользоваться, можно увидеть ниже:
#include <chrono> #include <iostream> class TimingUtil { public: TimingUtil(std::string const& message) : start_(std::chrono::steady_clock::now()), message_(message) { } ~TimingUtil() { std::chrono::steady_clock::time_point finish = std::chrono::steady_clock::now(); std::cout << message_ << " took " << std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start_).count() << " ns." << std::endl; } private: std::chrono::steady_clock::time_point start_; std::string message_; }; int main(int argc, char *argv[]) { TimingUtil t("My test"); for(u_int32_t i=0; i<0xFFFFFFFF; i++) { u_int32_t j = i; } return 0; }
В конструкторе класса можно задать имя теста, которое будет выведено на экран вместе с измеренным временем. Результат нашего теста будет таким:
My test took 151 ns.
Данный класс хорош тем, что использует парадигму RAII, что позволяет измерять время выполнения кода в пределах области видимости, избавляя от необходимости беспокоиться о том, что замеряемый участок кода может вернуть управление раньше или выбросит исключение. Измерение будет произведено в любом случае при уничтожении класса в момент выхода за пределы области видимости.
Свежие комментарии