Проблема измерения в N раз времени выполнения блока кода

EDIT: Я только что нашел свою проблему после написания этого длинного сообщения, объясняющего каждую мелочь... Если кто-то может дать мне хороший ответ о том, что я делаю неправильно, и как я могу получить время выполнения в секунд (используя число с плавающей запятой с 5 знаками после запятой или около того), я отмечу это как принятое. Подсказка: проблема заключалась в том, как я интерпретировал man-страницу clock_getttime().

Hi,

Допустим, у меня есть функция с именем myOperation, для которой мне нужно измерить время выполнения. Чтобы измерить это, я использую clock_gettime(), как было рекомендовано >здесь в одном из комментариев.

Мой учитель рекомендует нам измерить его N раза, чтобы мы могли получить среднее значение, стандартное отклонение и медиану для итогового отчета. Он также рекомендует нам выполнить myOperation M раз вместо одного. Если myOperation — очень быстрая операция, измерение ее M раз позволит нам получить представление о «реальном времени», которое она занимает; потому что используемые часы могут не иметь требуемой точности для измерения такой операции. Таким образом, выполнение myOperation только один раз или M раз действительно зависит от того, занимает ли сама операция достаточно времени для используемой нами точности часов.

У меня проблемы с выполнением M раз. Увеличение M уменьшает (намного) конечное среднее значение. Что не имеет для меня смысла. Это примерно так, в среднем вам требуется от 3 до 5 секунд, чтобы добраться из точки А в Б. Но затем вы идете из А в Б и обратно в А 5 раз (что составляет 10 раз, потому что из А в Б это то же самое, что и Б). к А), и вы измеряете это. Чем вы делите на 10, среднее значение, которое вы получаете, должно быть таким же средним, которое вы берете, путешествуя из точки А в Б, что составляет от 3 до 5 секунд.

Это то, что я хочу, чтобы мой код делал, но он не работает. Если я буду продолжать увеличивать количество раз, когда я иду из А в Б и обратно А, среднее значение будет с каждым разом все ниже и ниже, для меня это не имеет смысла.

Достаточно теории, вот мой код:

#include <stdio.h>
#include <time.h>

#define MEASUREMENTS 1
#define OPERATIONS   1

typedef struct timespec TimeClock;

TimeClock diffTimeClock(TimeClock start, TimeClock end) {
    TimeClock aux;

    if((end.tv_nsec - start.tv_nsec) < 0) {
        aux.tv_sec = end.tv_sec - start.tv_sec - 1;
        aux.tv_nsec = 1E9 + end.tv_nsec - start.tv_nsec;
    } else {
        aux.tv_sec = end.tv_sec - start.tv_sec;
        aux.tv_nsec = end.tv_nsec - start.tv_nsec;
    }

    return aux;
}

int main(void) {
    TimeClock sTime, eTime, dTime;
    int i, j;

    for(i = 0; i < MEASUREMENTS; i++) {
        printf(" » MEASURE %02d\n", i+1);

        clock_gettime(CLOCK_REALTIME, &sTime);

        for(j = 0; j < OPERATIONS; j++) {
            myOperation();
        }

        clock_gettime(CLOCK_REALTIME, &eTime);

        dTime = diffTimeClock(sTime, eTime);

        printf("   - NSEC (TOTAL): %ld\n", dTime.tv_nsec);
        printf("   - NSEC (OP): %ld\n\n", dTime.tv_nsec / OPERATIONS);
    }

    return 0;
}

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

Как видите, OPERATIONS = 1 и результаты такие:

 » MEASURE 01
   - NSEC (TOTAL): 27456580
   - NSEC (OP): 27456580

Для OPERATIONS = 100 результаты следующие:

 » MEASURE 01
   - NSEC (TOTAL): 218929736
   - NSEC (OP): 2189297

Для OPERATIONS = 1000 результаты следующие:

 » MEASURE 01
   - NSEC (TOTAL): 862834890
   - NSEC (OP): 862834

Для OPERATIONS = 10000 результаты таковы:

 » MEASURE 01
   - NSEC (TOTAL): 574133641
   - NSEC (OP): 57413

Я не гений математики, на самом деле это далеко не так, но для меня это не имеет никакого смысла. Я уже говорил об этом с другом, который работает со мной над этим проектом, и он тоже не может понять различий. Я не понимаю, почему значение становится все ниже и ниже, когда я увеличиваю OPERATIONS. Сама операция должна занимать одно и то же время (в среднем, конечно, не точно такое же время), независимо от того, сколько раз я ее выполняю.

Вы могли бы сказать мне, что это на самом деле зависит от самой операции, считываемых данных и что некоторые данные могут уже быть в кеше и бла-бла, но я не думаю, что проблема в этом. В моем случае myOperation читает 5000 строк текста из CSV-файла, разделяет значения на ; и вставляет эти значения в структуру данных. Для каждой итерации я разрушаю структуру данных и снова ее инициализирую.

Теперь, когда я думаю об этом, я также думаю, что есть проблема с измерением времени с помощью clock_gettime(), возможно, я неправильно его использую. Я имею в виду, посмотрите на последний пример, где OPERATIONS = 10000. Общее время, которое потребовалось, составило 574133641 нс, что составляет примерно 0,5 с; это невозможно, это заняло пару минут, так как я не выдержала, глядя в экран и пошла что-то есть.


person rfgamaral    schedule 12.05.2010    source источник
comment
Поскольку вы измеряете время настенных часов между двумя событиями, вы должны использовать CLOCK_MONOTONIC, а не CLOCK_REALTIME — последнее может измениться, если системное время изменится, первое не изменится.   -  person caf    schedule 13.05.2010
comment
Да, но почему системное время должно меняться, если я не делаю это вручную?   -  person rfgamaral    schedule 13.05.2010
comment
Потому что системный демон вроде ntpd изменил его? (Или системный администратор в общей системе?)   -  person caf    schedule 13.05.2010
comment
Хорошо, это вряд ли произойдет на моей тестовой машине. Но у вас есть точка зрения. Я просто использовал REALTIME вместо MONOTONIC, потому что я читал, что на некоторых машинах (даже в некоторых дистрибутивах Linux) MONOTONIC не реализован, и я хочу, чтобы мой учитель мог скомпилировать код (если его нет). Конечно, если это Mac clock_gettime(), его даже не существует (во всяком случае, не на Mac моего друга).   -  person rfgamaral    schedule 13.05.2010


Ответы (4)


Вам просто нужно изменить свою функцию diffTimeClock(), чтобы она возвращала разницу в количестве секунд, как double:

double diffTimeClock(TimeClock start, TimeClock end) {
    double diff;

    diff = (end.tv_nsec - start.tv_nsec) / 1E9;
    diff += (end.tv_sec - start.tv_sec);

    return diff;
}

и в основной подпрограмме измените dTime на double и printfs, чтобы он подходил:

printf("   - SEC (TOTAL): %f\n", dTime);
printf("   - SEC (OP): %f\n\n", dTime / OPERATIONS);
person caf    schedule 12.05.2010

Похоже, тип TimeClock имеет два поля: одно для секунд, а другое для наносекунд. Не имеет смысла просто делить наносекундное поле на количество операций. Вам нужно разделить общее время.

person bitc    schedule 12.05.2010

Если вы используете систему POSIX, в которой есть функция gettimeofday(), вы можете использовать что-то вроде этого, чтобы получить текущее время в микросекундах:

long long timeInMicroseconds(void) {
    struct timeval tv;

    gettimeofday(&tv,NULL);
    return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
}

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

long long start = timeInMicroseconds();
... do your task N times ...
printf("Total microseconds: %lld", timeInMicroseconds()-start);

Таким образом, вам не нужно иметь дело с двумя целыми числами, одно с секундами, а другое с микросекундами. Добавление и вычитание времени будет работать очевидным образом.

person antirez    schedule 12.05.2010
comment
Я не знаю, понял ли ты, но ты сделал именно то, что, по твоим словам, мне не нужно было бы иметь дело с двумя целыми числами. Посмотрите на return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;. Я сделал то же самое, чтобы решить свою первоначальную проблему, и у меня есть нано-точность вместо микро. - person rfgamaral; 13.05.2010
comment
Да, я имел в виду, не работайте с двумя целыми числами явно, когда вам нужно вычесть одно время в другое время, иначе становится сложнее проверить переполнение наносекунд (а затем вычесть 1 секунду и добавить 1000000 наносекунд к исходному результату) . Вместо того, как вы это сделали, и я предложил это, вы имеете дело с двумя целыми числами только в функции, создающей время в микросекундах. Весь остальной код будет просто использовать тривиальное единственное число. - person antirez; 13.05.2010
comment
Я делаю это уже в diffTimeClock() уже, поэтому я не очень понимаю ваш пост... - person rfgamaral; 13.05.2010

Я обычно использую для этого функцию time(). Он показывает время настенных часов, но в конце концов это действительно то, что меня волнует.

Одна проблема с тестированием производительности заключается в том, что операционная система может кэшировать операции, связанные с файловой системой. Таким образом, второй (и более поздние) запуски могут быть намного быстрее, чем первый запуск. Как правило, вам нужно протестировать несколько операций и усреднить результат, чтобы получить хорошее представление о результатах любых изменений, которые вы вносите. Существует так много переменных, что это может помочь вам отфильтровать шум.

person Jay    schedule 12.05.2010