Странные результаты при измерении дельта-времени в Linux - PullRequest
1 голос
/ 21 мая 2010

Обновление: исправлены дельта-вычисления в коде, но проблема остается

Ребята, не могли бы вы объяснить, почему время от времени я получаю очень странные результаты, используя следующий код:

#include <unistd.h>
#include <sys/time.h>
#include <stdio.h>

int main()
{
  struct timeval start, end;
  long mtime1, mtime2, diff;

  while(1)
  {
    gettimeofday(&start, NULL);

    usleep(2000);

    gettimeofday(&end, NULL);

    mtime1 = (start.tv_sec * 1000 + start.tv_usec/1000.0);
    mtime2 = (end.tv_sec * 1000 + end.tv_usec/1000.0);

    diff = mtime2 - mtime1;
    if(diff > 10) 
        printf("WTF: %ld\n", diff);
  }

  return 0;
}

(Вы можете скомпилировать и запустить его с: gcc test.c -o out -lrt && ./out)

Что я испытываюСпорадически большие значения diff могут изменяться почти каждую секунду или даже чаще, например:

$ gcc test.c -o out -lrt && ./out 
WTF: 14
WTF: 11
WTF: 11
WTF: 11
WTF: 14
WTF: 13
WTF: 13
WTF: 11
WTF: 16

Как это возможно?Виновата ли ОС?Слишком много переключений контекста?Но мой ящик простаивает (средняя загрузка: 0,02, 0,02, 0,3).

Вот моя версия ядра Linux:

$ uname -a
Linux kurluka 2.6.31-21-generic #59-Ubuntu SMP Wed Mar 24 07:28:56 UTC 2010 i686 GNU/Linux

Ответы [ 5 ]

4 голосов
/ 21 мая 2010

Функции сна гарантируют, что вы спите как минимум определенное время. Поскольку Linux не является операционной системой реального времени, вы НЕ МОЖЕТЕ быть уверены, что она будет находиться в спящем режиме ТОЛЬКО необходимое количество времени. Это проблема, так как вы НЕ МОЖЕТЕ рассчитывать на это значение. Как вы указали, случается, что время сна действительно БОЛЬШОЕ.

Планировщик Linux не может этого гарантировать. С ОС реального времени вы можете получить это.

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

#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <stdio.h>

int main()
{
  struct timeval start, end;
  long mtime, mtime2, start_time, end_time, seconds, useconds;

  while(1)
  {
    gettimeofday(&start, NULL);

    usleep(2000);

    gettimeofday(&end, NULL);

    seconds  = end.tv_sec  - start.tv_sec;
    useconds = end.tv_usec - start.tv_usec;

    mtime = ((seconds) * 1000 + useconds/1000.0) + 0.5;

    start_time = ((start.tv_sec) * 1000 + start.tv_usec/1000.0) + 0.5;
    end_time = ((end.tv_sec) * 1000 + end.tv_usec/1000.0) + 0.5;

    mtime2 = end_time - start_time;

    if(mtime > 10 || mtime2 > 10)
    {
      printf("WTF: %ld\n", mtime);
      printf("WTF2: %ld\n", mtime2);
    }
  }

  return 0;
}

Результаты:

$ gcc test.c -o out -lrt && ./out
WTF: 11
WTF2: 12
WTF: 21
WTF2: 21

Я подумал, что это неправильно, поскольку часть useconds является циклической и может привести к большой отрицательной разнице. Но это не приведет к такому большому времени, когда вы используете длинные целые числа со знаком ...

my2cents

Редактировать : от человека до сна:

Текущая реализация nanosleep () основан на нормальном механизм таймера ядра, который имеет разрешение 1 / Гц с (см. время (7)). Поэтому nanosleep () всегда делает паузу по крайней мере, на указанное время, однако это может занять до 10 мс дольше чем указано до процесса снова становится работоспособным Для того же причина, значение возвращается в случае доставленный сигнал в * rem обычно округляется до следующего большего кратного 1 / Гц с.

2 голосов
/ 21 мая 2010

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

С другой стороны, вы неправильно вычисляете mtime. Вот «процедура, которую я использую, хотя для struct timepec, а не struct timeval (наносекунды вместо микросекунд) принцип должен быть ясен:

timespec diff(timespec start, timespec end)
{
    timespec temp;
    if ((end.tv_nsec - start.tv_nsec) < 0) {
        temp.tv_sec = end.tv_sec - start.tv_sec - 1;
        temp.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec;
    } else {
        temp.tv_sec = end.tv_sec - start.tv_sec;
        temp.tv_nsec = end.tv_nsec - start.tv_nsec;
    }
    return temp;
}
1 голос
/ 21 мая 2010

Нашел, см. Справочную страницу

http://linux.die.net/man/3/usleep

по гранулярности системных таймеров.

что на 10 мс афаик. Таким образом, usleep может истечь задолго до того, как процесс будет перенесен.

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

0 голосов
/ 21 мая 2010

Я уже сделал такие меры, и мой вывод точно такой же. Происходит одинаково в Windows и Linux.

Программа, строящая гистограмму за 10 ^ -n секунд, дает следующие результаты.

0.1 0
0.01 0
0.001 0
0.0001 2
1e-05 24
1e-06 69726023
Total: 69726049
Duration: 6.47403 seconds.
Average: 0.0928495 microseconds.

Но учтите, что это совершенно новая система. Я помню, как использовал это год назад в системе 2004 года и имел пару ударов в секунду в диапазоне 0,01 (более 10 мс).

0 голосов
/ 21 мая 2010

Ваша формула неверна. Вы должны конвертировать оба раза в одном и том же масштабе. В вашем примере мс.

double mtime1 = (start.tv_sec * 1000 + start.tv_usec/1000.0) ;
double mtime2 = (end.tv_sec * 1000 + end.tv_usec/1000.0) ;

double diff = mtime2 - mtime1;
if(diff > 10) 
  printf("WTF: %ld\n", diff);

Вы должны вычесть исправленные значения

Пример: t1 = 1.999999 t2 = 2.000001 так интервал 2 мкс

С вашей формулой вы рассчитываете:

2 - 1 == 1 и 1 - 9999999 дают результат (1 * 1000 - 999998 / 1000) + 0.5 == 0.502, что явно неверно.

Мой метод дает:

mtime1 = (1 * 1000 + 999999 / 1000) = 1999.999
mtime2 = (2 * 1000 +      1 / 1000) = 2000.001

2000.001 - 1999.999 = 0.002 ms
...