cplus-plus.ru logo
Мы переехали на cplus-plus.ru
Главная страница В закладкиО сайтеКарта сайта
Хостинг от uCoz
Добавить в закладки

Меню сайта

Полезные ссылки

Наша рассылка
Подписаться на рассылку
"C++ : cplus-plus.ru :
Рассылка статей C++"


Друзья сайта
alsproject.ru Выбор выходного разделительного конденсатора

Приветствую Вас, Гость · rss 26-Апр-2024, 09:52
Главная » 2011 » Январь » 13 » Время выполнения операций в C++ под Linux
23:08
Время выполнения операций в C++ под Linux

Время выполнения операций в C++ под Linux
Сегодня речь пойдёт об использовании clock_gettime в качестве секундомера для определения времени выполнения разных участков Вашего кода. Если Вы всё это уже знаете, или предпочитаете использовать gprof или другой профайлер, то смело можете пропускать данный топик, если же Вас это заинтересовало — добро пожаловать под хабракат.

Возможно, многим из Вас эта тема будет не интересна в связи с тем, что Вы уже всё это давно знаете. А может просто предпочитаете профайлинг. Может Вы знаете более эффективный способ, и если так, то буду рад услышать Ваши комментарии.

Однажды, во время написания очередного теста, понял что простого вывода времени этапа тестирования мне недостаточно и решил написать небольшой класс.
Начнём с теории.

clock_gettime. Кто это и с чем его едят


Данная функция описана в <time.h> и имеет следующий синтаксис:

int clock_gettime(clockid_t clock_id, struct timespec *tp);

Функция изменяет значения полей в структуре tp, на текущие показатели часов. Каких часов, спросят наиболее искушённые? Тех чей тип был указан в clock_id.
По давней традиции функция возвращает 0 если выполнена успешно, и -1 если что-то привело к неудаче(соответственно в errno записывается код ошбки).

Структура timespec состоит всего из 2х полей:

struct timespec
{
__time_t tv_sec; /* Seconds. */
long int tv_nsec; /* Nanoseconds. */
};

в первое из которых записываются секунды, а во второе, как Вы могли уже догадаться из комментария, наносекунды.

Из не разобранного остались лишь типы часов. Разберём и их. Описаны они в <linux/time.h> и, думаю, могут у некоторых различаться:

#define CLOCK_REALTIME 0
#define CLOCK_MONOTONIC 1
#define CLOCK_PROCESS_CPUTIME_ID 2
#define CLOCK_THREAD_CPUTIME_ID 3
#define CLOCK_MONOTONIC_RAW 4
#define CLOCK_REALTIME_COARSE 5
#define CLOCK_MONOTONIC_COARSE 6


  • CLOCK_REALTIME — системные часы, со всеми их плюсами и
    минусами. Могут быть переведены вперёд/назад, да и вставные секунды
    иногда попадаются. Но иногда и их достаточно.
  • CLOCK_MONOTONIC — сродни системным часам, но, в
    отличии от них, представляют собой постоянно увеличивающийся счётчик, в
    связи с чем, естественно, не могут быть переведены. Обычно равно
    аптайму системы.
  • CLOCK_PROCESS_CPUTIME_ID — возвращает время
    затрачиваемое процессором относительно нашего приложения. В нашем
    случае, используя эти часы мы получим время затраченое процессором на
    работу с нашим приложением в независимости от других задач системы.
  • CLOCK_THREAD_CPUTIME_ID — похоже на CLOCK_PROCESS_CPUTIME_ID но более частный случай, так как следит за временем затрачиваемым на текущий поток.
  • CLOCK_MONOTONIC_RAW — то же что и CLOCK_MONOTONIC, но в отличии от первого не подвержен изменению через NTP.

Последние два я даже не смог протестировать ни на одной машине из доступных — везде они возвращали нули, но, по описанию автора, эти часы оперируют с тиками, и могут быть очень неточны в некоторых ситуациях, хотя в других дают беспрецедентную точность.

Программная часть не несёт ничего гениального, а местами, и вовсе выглядит по индуски.

Класс GetWorkTime

Для начала описал будущий класс. Думаю назначение переменных и и функций-членов понятны из названий.

GetWorkTime.h

#include <time.h>

class GetWorkTime
{
private:
bool isCounterStarted;
bool isCounterWorkComplete;

clockid_t clockID;
timespec startTime, endTime;

public:
GetWorkTime(clockid_t clockID = CLOCK_REALTIME);
virtual ~GetWorkTime(){};

void setClockID(clockid_t clockID);
void countingStart();
void countingEnd();

unsigned long long getWorkTimeInNanoseconds();
unsigned long long getWorkTimeInSeconds();
};


Реализация тоже не отличается гениальностью — всё ожидаемо.

GetWorkTime.cpp

#include "GetWorkTime.h"

#include <stdio.h>
#include <unistd.h>

GetWorkTime::GetWorkTime(clockid_t clockID)
{
isCounterStarted = false;
isCounterWorkComplete = false;

setClockID(clockID);
}

void GetWorkTime::setClockID(clockid_t clockID)
{
switch (clockID)
{
#ifdef _POSIX_MONOTONIC_CLOCK
case CLOCK_MONOTONIC:
case CLOCK_MONOTONIC_RAW:
#endif
#ifdef _POSIX_CPUTIME
case CLOCK_PROCESS_CPUTIME_ID:
#endif
#ifdef _POSIX_THREAD_CPUTIME
case CLOCK_THREAD_CPUTIME_ID:
#endif
case CLOCK_REALTIME_COARSE:
case CLOCK_MONOTONIC_COARSE:
this->clockID = clockID;
break;

default:
this->clockID = CLOCK_REALTIME;
}
}

void GetWorkTime::countingStart()
{
if (!isCounterStarted)
{
isCounterStarted = true;
isCounterWorkComplete = false;

clock_gettime(clockID, &startTime);
}
else
perror("WARNING: Счётчик уже запущен\n");
}

void GetWorkTime::countingEnd()
{
if (isCounterStarted)
{
clock_gettime(clockID, &endTime);

isCounterStarted = false;
isCounterWorkComplete = true;
}
else
perror("WARNING: Невозможно остановить не запущен\n");
}

unsigned long long GetWorkTime::getRealNanosecondsCount(timespec time)
{
return (unsigned long long)time.tv_sec * 1000000000 + time.tv_nsec;
}

unsigned long long GetWorkTime::getCurrentTimeInNanoseconds()
{
clock_gettime(clockID, &currentTime);

return getRealNanosecondsCount(currentTime);
}

unsigned long GetWorkTime::getCurrentTimeInSeconds()
{
clock_gettime(clockID, &currentTime);

return (unsigned long)currentTime.tv_sec;
}

unsigned long long GetWorkTime::getWorkTimeInNanoseconds()
{
if (isCounterWorkComplete)
return getRealNanosecondsCount(endTime) - getRealNanosecondsCount(startTime);
else
perror("ERROR: Невозможно узнать время выполнения не запустив, а затем остановив, счётчик.\n");

return 0;
}

unsigned long GetWorkTime::getWorkTimeInSeconds()
{
if (isCounterWorkComplete)
return (unsigned long)endTime.tv_sec - startTime.tv_sec;
else
perror("ERROR: Невозможно узнать время выполнения не запустив, а затем остановив, счётчик.\n");

return 0;
}

Тесты

Для демонстрации работы написал небольшую обёртку вызывающую счётчик для разных задач и с разными часами.

Функция testTime последовательно пытается определить чистое нулевое время замера(интервал между началом отсчёта и окончанием без использования класса-обёртки), после чего замеряет тот же интервал, но уже с использованием класса.
Далее считается сколько времени займёт создание класса, ресурсоёмкая задача и sleep(0).

Результаты работы на моей машине приведены ниже.

CLOCK_REALTIME
Текущее время: 1294839189719051200нс, 1294839189с
clock_gettime(clockID, &startTime);clock_gettime(clockID, &endTime); выполнилось за: 439нс, 0с
counter.countingStart();counter.countingEnd(); выполнилось за: 465нс, 0с
Создание и удаление объекта GetWorkTime заняло: 71454нс, 0с
Выполнение for (int i = 0; i < 1000000000; i++) testDouble = testDouble / M_PI;: 7397920416нс, 8с
Выполнение sleep(0) заняло: 11040нс, 0с

CLOCK_MONOTONIC
Текущее время: 533621337167908нс, 533621с
clock_gettime(clockID, &startTime);clock_gettime(clockID, &endTime); выполнилось за: 450нс, 0с
counter.countingStart();counter.countingEnd(); выполнилось за: 465нс, 0с
Создание и удаление объекта GetWorkTime заняло: 1943нс, 0с
Выполнение for (int i = 0; i < 1000000000; i++) testDouble = testDouble / M_PI;: 6847570003нс, 7с
Выполнение sleep(0) заняло: 993нс, 0с

CLOCK_MONOTONIC_RAW
Текущее время: 533628051394051нс, 533628с
clock_gettime(clockID, &startTime);clock_gettime(clockID, &endTime); выполнилось за: 442нс, 0с
counter.countingStart();counter.countingEnd(); выполнилось за: 450нс, 0с
Создание и удаление объекта GetWorkTime заняло: 2043нс, 0с
Выполнение for (int i = 0; i < 1000000000; i++) testDouble = testDouble / M_PI;: 6810908498нс, 6с
Выполнение sleep(0) заняло: 979нс, 0с

CLOCK_PROCESS_CPUTIME_ID
Текущее время: 19781932013нс, 19с
clock_gettime(clockID, &startTime);clock_gettime(clockID, &endTime); выполнилось за: 667нс, 0с
counter.countingStart();counter.countingEnd(); выполнилось за: 693нс, 0с
Создание и удаление объекта GetWorkTime заняло: 2353нс, 0с
Выполнение for (int i = 0; i < 1000000000; i++) testDouble = testDouble / M_PI;: 6579920922нс, 7с
Выполнение sleep(0) заняло: 1292нс, 0с

CLOCK_THREAD_CPUTIME_ID
Текущее время: 26361905552нс, 26с
clock_gettime(clockID, &startTime);clock_gettime(clockID, &endTime); выполнилось за: 622нс, 0с
counter.countingStart();counter.countingEnd(); выполнилось за: 641нс, 0с
Создание и удаление объекта GetWorkTime заняло: 2281нс, 0с
Выполнение for (int i = 0; i < 1000000000; i++) testDouble = testDouble / M_PI;: 6587131651нс, 6с
Выполнение sleep(0) заняло: 1210нс, 0с

rdtsc()
startTime = rdtsc(); endTime = rdtsc();выполнилось за: 91 тактов
Создание и удаление объекта GetWorkTime заняло: 3353 тактов
Выполнение for (int i = 0; i < 1000000000; i++) testDouble = testDouble / M_PI;: 14028721707 тактов
Выполнение sleep(0) заняло: 1113 тактов

Ах да, чуть не забыл, добавьте Вашему gcc ключик -lrt для того что бы clock_gettime заработал.

Добавлены тесты, и результаты этих самых тестов, с использованием rdtsc, в связи с чем несколько изменены исходные коды.
Прикрутил к велосипеду по определению времени в неаносекундах новые педали, должно быть получше, но есть идея как сделать ещё лучше.
Думаю, после праздников добавлю.
Источник: http://habrahabr.ru
Категория: Новости | Просмотров: 1619 | Добавил: FazaNaka | Рейтинг: 5.0/2
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]