Какое лучшее временное разрешение я могу получить в Linux

Я пытаюсь измерить разницу во времени между двумя сигналами на параллельном порту, но сначала я узнал, насколько точна и точна моя измерительная система (AMD Athlon (tm) 64 X2 Dual Core Processor 5200+ × 2 ) на SUSE 12.1 x64.

Итак, после некоторого чтения я решил использовать clock_gettime(), сначала я получаю значение clock_getres() с помощью этого кода:

/*
 * This program prints out the clock resolution.
 */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main( void )
  {
    struct timespec res;

    if ( clock_getres( CLOCK_REALTIME, &res) == -1 ) {
      perror( "clock get resolution" );
      return EXIT_FAILURE;
    }
    printf( "Resolution is %ld nano seconds.\n",
          res.tv_nsec);
    return EXIT_SUCCESS;
  }

а выход был: 1 nano second. И я был так счастлив!

Но вот моя проблема, когда я попытался проверить этот факт с помощью этого другого кода:

#include <iostream>
#include <time.h>
using namespace std;

timespec diff(timespec start, timespec end);

int main()
{
    timespec time1, time2, time3,time4;
    int temp;
    time3.tv_sec=0;
    time4.tv_nsec=000000001L;
    clock_gettime(CLOCK_REALTIME, &time1);
        NULL;
    clock_gettime(CLOCK_REALTIME, &time2);
    cout<<diff(time1,time2).tv_sec<<":"<<diff(time1,time2).tv_nsec<<endl;
    return 0;
}

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;
}

это вычисляет время между двумя вызовами clock_gettime, time3 и time4 объявляются, но не используются в этом примере, потому что я делал с ними тесты.

Выход в этом примере колеблется между 978 и 1467 нс. оба числа кратно 489, это заставляет меня думать, что 489 нс - это мое РЕАЛЬНОЕ разрешение. далекого от 1 нс, полученного выше.

Мой вопрос: есть ли ЛЮБОЙ ПУТЬ получения лучших результатов? я что-то пропустил?

Мне действительно нужно разрешение не менее 10ns для моего проекта. Давай! GPS может получить лучшее разрешение, чем ПК?

Ответ 1

Насколько я знаю, Linux , работающий на ПК, как правило, не сможет дать вам точность таймера в диапазоне наносекунд. Это в основном связано с типом планировщика задач/процессов, используемого в ядре. Это в значительной степени является результатом ядра, как и аппаратного обеспечения.

Если вам нужно время с разрешением наносекунд, я боюсь, что вам не повезло. Однако вы должны иметь разрешение на микросекунду, которое должно быть достаточно хорошим для большинства сценариев, включая приложение с параллельным портом.

Если вам нужно время в диапазоне наносекунд, чтобы быть точным на нано-секунду, вам, скорее всего, понадобится специальное аппаратное решение; с действительно точным осциллятором (для сравнения базовая тактовая частота большинства процессоров x86 находится в диапазоне мегагерц перед множителями)

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

Ответ 2

Я понимаю, что эта тема давно мертва, но хотела бросить мои выводы. Это длинный ответ, поэтому я поставил здесь короткий ответ, и те, у кого есть терпение, могут пробраться через остальное. Не совсем ответ на вопрос - это 700 нс или 1500 нс в зависимости от того, какой режим используется clock_gettime(). Длительный ответ более сложный.

Для справки, машина, на которой я работал, - это старый ноутбук, которого никто не хотел. Это Acer Aspire 5720Z, работающий под Ubuntu 14.041 LTS.

Аппаратное обеспечение:
RAM: 2.0 GiB//Вот как Ubuntu сообщает об этом в "Системные настройки" → "Подробности"
Процессор: Intel® Pentium (R) Двойной процессор T2330 @1,60 ГГц × 2
Графика: Intel® 965GM x86/MMX/SSE2

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

Из-за этого вопроса я решил, что clock_gettime() выглядит так, как будто он отвечает моим потребностям. Но мой опыт работы с аппаратными средствами ПК в прошлом оставил меня поджатым, поэтому я начал с нескольких экспериментов, чтобы узнать, что такое фактическое разрешение таймера.

Метод: собирайте последовательные образцы результата из clock_gettime() и просматривайте любые шаблоны в разрешении. Далее следует код.

Результаты немного дольше Резюме:
1. Не совсем результат. Указанная резолюция полей в структуре находится в наносекундах. Результатом вызова функции clock_getres() также является tv_sec 0, tv_nsec 1. Однако предыдущий опыт учил не доверять разрешению только из одной структуры. Это верхний предел точности и реальности, как правило, намного сложнее. 2. Фактическое разрешение результата clock_gettime() на моей машине, с моей программой, с моей операционной системой, в один конкретный день и т.д., Оказывается, составляет 70 наносекунд для режима 0 и 1. 70 нс не так уж плохо, но, к сожалению, это нереально, как мы увидим в следующем пункте. Чтобы усложнить ситуацию, при использовании режимов 2 и 3 разрешение составляет 7 нс.
3. Длительность вызова clock_gettime() больше похожа на 1500 нс для режимов 0 и 1. Мне вообще не имеет смысла требовать разрешение 70 нс на время, если для получения значения требуется 20 раз разрешение.
4. Некоторые режимы clock_gettime() быстрее других. Режимы 2 и 3, очевидно, составляют половину времени настенных часов мод 0 и 1. Режимы 0 и 1 статистически неотличимы друг от друга. Режимы 2 и 3 намного быстрее, чем режимы 0 и 1, причем наиболее оптимальным является режим 3.

Прежде чем продолжить, я лучше определяю режимы: в каком режиме:?: Режим 0 CLOCK_REALTIME//ссылка: http://linux.die.net/man/3/clock_gettime
Режим 1 CLOCK_MONOTONIC
Режим 2 CLOCK_PROCESS_CPUTIME_ID
Режим 3 CLOCK_THREAD_CPUTIME_ID

Заключение: Для меня нет смысла говорить о разрешении временных интервалов, если разрешение меньше времени, которое функция выполняет для получения временного интервала. Например, если мы используем режим 3, мы знаем, что функция завершается в течение 700 наносекунд в 99% случаев. И мы также знаем, что временной интервал, который мы получим, будет кратным 7 наносекундам. Таким образом, "разрешение" 7 наносекунд, составляет 1/100-й раз, чтобы сделать звонок, чтобы получить время. Я не вижу никакого значения в интервале изменения 7 наносекунд. Существует 3 разных ответа на вопрос о разрешении: 1 нс, 7 или 70 нс и, наконец, 700 или 1500 нс. Я одобряю последнюю цифру.

После того, как все сказано и сделано, если вы хотите измерить производительность какой-либо операции, вам нужно иметь в виду, сколько времени занимает вызов clock_gettime() - это 700 или 1500 нс. Нет смысла пытаться измерить то, что занимает 7 наносекунд, например. Ради аргументов, скажем, вы были готовы жить с 1% -ой ошибкой в ​​своих выводах теста производительности. Если вы используете режим 3 (который, я думаю, буду использовать в моем проекте), вам нужно сказать, что интервал, который вам нужно измерить, должен быть 100 раз 700 наносекунд или 70 микросекунд. В противном случае ваши выводы будут иметь более 1% ошибки. Итак, продолжайте и оценивайте свой код интереса, но если ваше прошедшее время в интересующем коде меньше 70 микросекунд, тогда вам лучше пойти и запрограммировать код интереса достаточно, чтобы интервал был больше как 70 микросекунд или более.

Обоснование этих претензий и некоторых деталей:

Требование 3 в первую очередь. Это достаточно просто. Просто запустите clock_gettime() большое количество раз и запишите результаты в массиве, а затем обработайте результаты. Выполняйте обработку вне цикла, чтобы время между вызовами clock_gettime() было как можно короче.

Что все это значит? См. График. Например, для режима 0 вызов функции clock_gettime() занимает большую часть времени менее 1,5 микросекунд. Вы можете видеть, что режим 0 и режим 1 в основном одинаковы. Однако режимы 2 и 3 очень сильно отличаются от режимов 0 и 1 и немного отличаются друг от друга. Режимы 2 и 3 занимают примерно половину времени настенных часов для clock_gettime() по сравнению с режимами 0 и 1. Также обратите внимание, что режим 0 и 1 немного отличаются друг от друга - в отличие от режимов 2 и 3. Обратите внимание, что режим 0 и 1 отличаются на 70 наносекундов - это число, которое мы вернем в заявку № 2.

Прилагаемый график ограничен диапазоном до 2 микросекунд. В противном случае выбросы в данных предотвращают передачу графика предыдущей точкой. Что-то на графике не ясно, так это то, что выбросы для режимов 0 и 1 намного хуже, чем выбросы для режимов 2 и 3. Другими словами, не только средний и статистический "режим" (значение, которое происходит наиболее) и медиана (т.е. 50-й процентиль) для всех этих режимов различны, поэтому есть максимальные значения и их 99-й процентили.

График прилагается для 100 001 выборки для каждого из четырех режимов. Обратите внимание, что в графических тестах использовалась только маска процессора процессора 0. Независимо от того, пользовался ли я приемом процессора или нет, похоже, не имеет никакого значения для графика.

Претензия 2: Если вы внимательно посмотрите на образцы, собранные при подготовке графика, вы скоро заметите, что разница между различиями (то есть различиями 2-го порядка) относительно постоянна - примерно в 70 наносекунд (fore Modes 0 и 1 at наименее). Чтобы повторить этот эксперимент, собирайте 'n' образцы тактового времени, как и раньше. Затем вычислите различия между каждым образцом. Теперь отсортируйте различия в порядке (например, sort -g), а затем выведите отдельные уникальные отличия (например, uniq -c).

Например:

$ ./Exp03 -l 1001 -m 0 -k | sort -g | awk -f mergeTime2.awk | awk -f percentages.awk | sort -g
1.118e-06 8 8 0.8 0.8       // time,count,cumulative count, count%, cumulative count%
1.188e-06 17 25 1.7 2.5
1.257e-06 9 34 0.9 3.4
1.327e-06 570 604 57 60.4
1.397e-06 301 905 30.1 90.5
1.467e-06 53 958 5.3 95.8
1.537e-06 26 984 2.6 98.4
<snip>

Разница между длительностью в первом столбце часто составляет 7e-8 или 70 наносекунд. Это может стать более ясным путем обработки различий:

$ <as above> | awk -f differences.awk 
7e-08
6.9e-08
7e-08
7e-08
7e-08
7e-08
6.9e-08
7e-08
2.1e-07 // 3 lots of 7e-08
<snip>

Обратите внимание, как все различия являются целыми кратными 70 наносекундам? Или, по крайней мере, с ошибкой округления 70 наносекунд.

Этот результат вполне может быть зависимым от аппаратного обеспечения, но на самом деле я не знаю, что ограничивает его до 70 наносекунд. Может быть, есть генератор 14,28 МГц где-то?

Обратите внимание, что на практике я использую гораздо большее количество образцов, таких как 100 000, а не 1000, как указано выше.

Соответствующий код (прилагается):

'Expo03' - это программа, которая называет clock_gettime() как можно быстрее. Обратите внимание, что типичное использование будет выглядеть примерно так:

./Expo03 -l 100001 -m 3

Это вызовет clock_gettime() 100 001 раз, чтобы мы могли вычислить 100 000 различий. Каждый вызов функции clock_gettime() в этом примере будет использовать режим 3.

MergeTime2.awk - полезная команда, которая является прославленной командой "uniq". Проблема заключается в том, что различия 2-го порядка часто встречаются в парах 69 и 1 наносекунды, а не 70 (для режима 0 и 1, по крайней мере), так как я довел вас до сих пор. Поскольку нет разницы в 68 наносекунд или разницы в 2 наносекунды, я объединил эти 69 и 1 наносекундные пары в одно число 70 наносекунд. Почему поведение 69/1 происходит вообще, интересно, но рассматривая их как два отдельных номера, в основном добавляли "шум" к анализу.

Прежде чем вы спросите, я повторил это упражнение, избегая плавающей запятой, и та же проблема все еще возникает. В результате tv_nsec как целое число имеет это поведение 69/1 (или 1/7 и 1/6), поэтому, пожалуйста, не предполагайте, что это артефакт, вызванный вычитанием с плавающей запятой.

Обратите внимание, что я уверен в этом "упрощении" для 70 нс и для небольших целых кратных 70 нс, но этот подход выглядит менее надежным для случая 7 нс, особенно когда вы получаете разности 2-го порядка в 10 раз больше 7 нс разрешение.

percentages.awk и difference.awk прилагаются в случае.

Остановить печать: я не могу опубликовать график, поскольку у меня нет "репутации не менее 10". Извините "это.

Роб Уотсон 21 ноября 2014 г.

Expo03.cpp

/* Like Exp02.cpp except that here I am experimenting with
   modes other than CLOCK_REALTIME
   RW 20 Nov 2014
*/

/* Added CPU affinity to see if that had any bearing on the results
   RW 21 Nov 2014
*/

#include <iostream>
using namespace std;
#include <iomanip>

#include <stdlib.h> // getopts needs both of these
#include <unistd.h>

#include <errno.h> // errno

#include <string.h> // strerror()

#include <assert.h>

// #define MODE CLOCK_REALTIME
// #define MODE CLOCK_MONOTONIC
// #define MODE CLOCK_PROCESS_CPUTIME_ID
// #define MODE CLOCK_THREAD_CPUTIME_ID

int main(int argc, char ** argv)
{
  int NumberOf = 1000;
  int Mode = 0;
  int Verbose = 0;
  int c;
  // l loops, m mode, h help, v verbose, k masK


  int rc;
  cpu_set_t mask;
  int doMaskOperation = 0;

  while ((c = getopt (argc, argv, "l:m:hkv")) != -1)
  {
    switch (c)
      {
      case 'l': // ell not one
        NumberOf = atoi(optarg);
        break;
      case 'm':
        Mode = atoi(optarg);
        break;
      case 'h':
        cout << "Usage: <command> -l <int> -m <mode>" << endl
             << "where -l represents the number of loops and "
             << "-m represents the mode 0..3 inclusive" << endl
             << "0 is CLOCK_REALTIME" << endl
             << "1 CLOCK_MONOTONIC" <<  endl
             << "2 CLOCK_PROCESS_CPUTIME_ID" << endl
             << "3 CLOCK_THREAD_CPUTIME_ID" << endl;
        break;
      case 'v':
        Verbose = 1;
        break;
      case 'k': // masK - sorry! Already using 'm'...
        doMaskOperation = 1;
        break;
      case '?':
        cerr << "XXX unimplemented! Sorry..." << endl;
        break;
      default:
        abort();
      }
  }

  if (doMaskOperation)
  {
    if (Verbose)
    {
      cout << "Setting CPU mask to CPU 0 only!" << endl;
    }
    CPU_ZERO(&mask);
    CPU_SET(0,&mask);
    assert((rc = sched_setaffinity(0,sizeof(mask),&mask))==0);
  }

  if (Verbose) {
    cout << "Verbose: Mode in use: " << Mode << endl;
  }

  if (Verbose)
  {
    rc = sched_getaffinity(0,sizeof(mask),&mask);
    // cout << "getaffinity rc is " << rc << endl;
    // cout << "getaffinity mask is " << mask << endl;
    int numOfCPUs = CPU_COUNT(&mask);
    cout << "Number of CPU is " << numOfCPUs << endl;
    for (int i=0;i<sizeof(mask);++i) // sizeof(mask) is 128 RW 21 Nov 2014
    {
      if (CPU_ISSET(i,&mask))
      {
        cout << "CPU " << i << " is set" << endl;
      }
      //cout << "CPU " << i 
      //     << " is " << (CPU_ISSET(i,&mask) ? "set " : "not set ") << endl;
    }
  }

  clockid_t cpuClockID;
  int err = clock_getcpuclockid(0,&cpuClockID);
  if (Verbose)
  {
    cout << "Verbose: clock_getcpuclockid(0) returned err " << err << endl;
    cout << "Verbose: clock_getcpuclockid(0) returned cpuClockID " 
       << cpuClockID << endl;
  }

  timespec timeNumber[NumberOf];
  for (int i=0;i<NumberOf;++i)
  {
    err = clock_gettime(Mode, &timeNumber[i]);
    if (err != 0) {
      int errSave = errno;
      cerr << "errno is " << errSave 
           << " NumberOf is " << NumberOf << endl;
      cerr << strerror(errSave) << endl;
      cerr << "Aborting due to this error" << endl;
      abort();
    }
  }

  for (int i=0;i<NumberOf-1;++i)
  {
    cout << timeNumber[i+1].tv_sec - timeNumber[i].tv_sec
            + (timeNumber[i+1].tv_nsec - timeNumber[i].tv_nsec) / 1000000000.
         << endl;

  }
  return 0;
}

MergeTime2.awk

BEGIN {
 PROCINFO["sorted_in"] = "@ind_num_asc"
}

{array[$0]++}

END {
  lastX = -1;
  first = 1;

  for (x in array)
  {
    if (first) { 
      first = 0 
      lastX = x; lastCount = array[x]; 
    } else {
      delta = x - lastX;
      if (delta < 2e-9) { # this is nasty floating point stuff!!
        lastCount += array[x]; 
        lastX = x
      } else {
        Cumulative += lastCount;
        print lastX "\t" lastCount "\t" Cumulative
        lastX = x; 
        lastCount = array[x]; 
      }
    }
  }
  print lastX "\t" lastCount "\t" Cumulative+lastCount
}

percentages.awk

{ # input is $1 a time interval $2 an observed frequency (i.e. count)
  # $3 is a cumulative frequency
  b[$1]=$2;
  c[$1]=$3;
  sum=sum+$2
} 

END {
  for (i in b) print i,b[i],c[i],(b[i]/sum)*100, (c[i]*100/sum);
}

differences.awk

NR==1 {
  old=$1;next
} 
{
  print $1-old;
  old=$1
}
NR==1 {
  old=$1;next
} 
{
  print $1-old;
  old=$1
}

Ответ 3

RDTSCP на вашем AMD Athlon 64 X2 вы получите счетчик штампа времени с разрешением, зависящим от ваших часов. Однако точность отличается от разрешения, вам необходимо зафиксировать привязанность потоков и отключить прерывания (см. Маршрутизацию IRQ).

Это означает отказ от ассемблера или для разработчиков Windows с помощью MSVC 2008.

RedHat с RHEL5 представил пользовательские прокладки, которые заменяют gettimeofday на вызовы с высоким разрешением RDTSCP:

Кроме того, проверьте свое оборудование на AMD 5200 с тактовой частотой 2,6 ГГц с интервалом 0,4 нс, а стоимость gettimeofday с RDTSCP составляет 221 цикл, который в лучшем случае равен 88ns.