1

我正在尝试测量并行端口上 2 个信号之间的时间差,但首先我要知道我在 SUSE 上的测量系统(AMD Athlon(tm) 64 X2 Dual Core Processor 5200+ × 2)有多准确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纳秒。我很高兴!

但这是我的问题,当我试图用其他代码检查这个事实时:

#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 ns 之间波动。这两个数字都是 489 的倍数,这让我觉得 489 ns 是我的真实分辨率。与上面获得的 1 ns 相差甚远。

我的问题:有没有办法获得更好的结果?我错过了什么吗?

我的项目确实需要至少 10ns 的分辨率。来吧!GPS可以获得比PC更好的分辨率?

4

3 回答 3

5

我意识到这个话题早就死了,但想把我的发现扔进去。这是一个很长的答案,所以我把简短的答案放在这里,有耐心的人可以完成剩下的。这个问题的答案不完全是 700 ns 或 1500 ns,具体取决于您使用的 clock_gettime() 模式。长答案要复杂得多。

作为参考,我做这项工作的机器是一台没人想要的旧笔记本电脑。它是运行 Ubuntu 14.041 LTS 的 Acer Aspire 5720Z。

硬件:
RAM:2.0 GiB // 这是 Ubuntu 在“系统设置”→“详细信息”中报告的方式
处理器:Intel® Pentium(R) Dual CPU T2330 @ 1.60GHz × 2
图形:Intel® 965GM x86/MMX/SSE2

我想在即将到来的项目中准确测量时间,并且作为一个相对较新的 PC 硬件,不管操作系统如何,我想我会在计时硬件的分辨率上做一些实验。我偶然发现了这个问题。

由于这个问题,我认为clock_gettime() 看起来符合我的需求。但是我过去在 PC 硬件方面的经验让我感到不知所措,所以我重新开始做一些实验,看看计时器的实际分辨率是多少。

方法:从clock_gettime() 收集结果的连续样本,并查看分辨率中的任何模式。代码如下。

结果略长摘要:

  1. 并不是真正的结果。结构中场的规定分辨率以纳秒为单位。调用clock_getres() 的结果也是tv_sec 0,tv_nsec 1。但以前的经验告诉我们不要只相信结构的分辨率。这是精度的上限,而现实往往要复杂得多。
  2. 在我的机器上,我的程序,我的操作系统,在某一天等上,clock_gettime() 结果的实际分辨率对于模式 0 和 1 来说是 70 纳秒。70 ns 并不算太糟糕,但不幸的是,这是不现实,我们将在下一点看到。更复杂的是,使用模式 2 和 3 时的分辨率似乎为 7 ns。
  3. 对于模式 0 和 1,clock_gettime() 调用的持续时间更像是 1500 ns。如果需要 20 倍的分辨率才能获得一个值,那么在时间上要求 70 ns 的分辨率对我来说根本没有意义。
  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,我们知道该函数在 99% 的时间内在 700 纳秒内完成。我们进一步知道,我们返回的时间间隔将是 7 纳秒的倍数。所以 7 纳秒的“分辨率”是调用时间的 1/100。我在 7 纳秒的更改间隔中看不到任何值。分辨率问题有 3 个不同的答案:1 ns、7 或 70 ns,最后是 700 或 1500 ns。我赞成最后一个数字。

毕竟,如果你想衡量某个操作的性能,你需要记住 clock_gettime() 调用需要多长时间——即 700 或 1500 ns。例如,尝试测量需要 7 纳秒的东西是没有意义的。为了争论,假设您愿意忍受性能测试结论的 1% 错误。如果使用模式 3(我想我将在我的项目中使用),您将不得不说您需要测量的时间间隔需要是 100 乘以 700 纳秒或 70 微秒。否则你的结论会有超过1%的错误。所以继续测量你感兴趣的代码,但是如果你在感兴趣的代码中经过的时间少于 70 微秒,

这些说法的理由和一些细节:

先声明3。这很简单。只需多次运行clock_gettime()并将结果记录在一个数组中,然后对结果进行处理。在循环之外进行处理,以使 clock_gettime() 调用之间的时间尽可能短。

这一切意味着什么?见附图。例如,对于模式 0,对 clock_gettime() 的调用在大多数情况下花费的时间不到 1.5 微秒。可以看到mode 0和mode 1基本一样。但是,模式 2 和 3 与模式 0 和 1 非常不同,并且彼此之间也略有不同。与模式 0 和 1 相比,模式 2 和 3 占用的时钟时间大约是时钟时间的一半。另请注意,模式 0 和 1 彼此略有不同 - 与模式 2 和 3 不同。请注意,模式 0 和 1 不同70 纳秒——这是我们将在权利要求 2 中提到的数字。

所附图表的范围限制为 2 微秒。否则,数据中的异常值会阻止图形传达前一点。图表没有说明的是,模式 0 和 1 的异常值比模式 2 和 3 的异常值差得多。换句话说,不仅平均值和统计“模式”(发生的值最大)和所有这些模式的中位数(即第 50 个百分位数)不同,因此最大值和它们的第 99 个百分位数也不同。

所附图表是四种模式中每一种模式的 100,001 个样本。请注意,绘制的测试仅使用处理器 0 的 CPU 掩码。我是否使用 CPU 亲和性似乎对图表没有任何影响。

主张 2:如果您在准备图表时仔细观察收集的样本,您很快就会注意到差异之间的差异(即二阶差异)是相对恒定的——大约为 70 纳秒(至少在模式 0 和 1 之前)。要重复此实验,请像以前一样收集“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 MHz 振荡器?

请注意,在实践中,我使用了更多的样本,例如 100,000,而不是上面的 1000。

相关代码(附):

'Expo03' 是尽可能快地调用clock_gettime() 的程序。请注意,典型用法类似于:

./Expo03 -l 100001 -m 3

这将调用 clock_gettime() 100,001 次,以便我们可以计算 100,000 个差异。本例中对 clock_gettime() 的每次调用都将使用模式 3。

MergeTime2.awk 是一个有用的命令,它是一个美化的“uniq”命令。问题是二阶差异通常是成对的 69 和 1 纳秒,而不是 70(至少对于模式 0 和 1),因为我让你相信到目前为止。因为没有 68 纳秒或 2 纳秒的差异,所以我将这 69 和 1 纳秒对合并为一个 70 纳秒的数字。为什么会发生 69/1 行为很有趣,但是将它们视为两个独立的数字通常会在分析中增加“噪音”。

在你问之前,我已经重复了这个避免浮点的练习,但同样的问题仍然存在。结果 tv_nsec 作为整数具有这种 69/1 行为(或 1/7 和 1/6),因此请不要假设这是由浮点减法引起的伪影。

请注意,我对 70 ns 和 70 ns 的小整数倍的这种“简化”很有信心,但这种方法在 7 ns 的情况下看起来不太稳健,尤其是当您获得 10 倍于 7 ns 分辨率的二阶差异时。

percents.awk 和 Difference.awk 以防万一。

停止新闻:我无法发布图表,因为我没有“至少 10 的声誉”。对不起。

罗布·沃森 2014 年 11 月 21 日

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

合并时间2.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
}

百分比.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);
}

差异.awk

NR==1 {
  old=$1;next
} 
{
  print $1-old;
  old=$1
}
于 2014-11-21T01:59:31.773 回答
3

据我所知,在 PC 上运行的Linux通常无法为您提供纳秒范围内的计时器精度。这主要是由于内核中使用的任务/进程调度器的类型。这既是内核的结果,也是硬件的结果。

如果您需要纳秒级分辨率的计时,恐怕您不走运。但是,您应该能够获得微秒级的分辨率,这对于大多数情况来说应该足够好 - 包括您的并行端口应用程序。

如果您需要纳秒范围内的计时以精确到纳秒,您很可能需要专用的硬件解决方案;具有非常精确的振荡器(作为比较,大多数 x86 CPU 的基本时钟频率在乘法器之前的兆赫兹范围内)

最后,如果您希望用您的计算机替换示波器的功能,那么它在相对低频信号之外就无法工作。你最好投资一个示波器——即使是一个简单的、便携式的、插入你的计算机以显示数据的手持式。

于 2012-09-28T16:15:13.643 回答
2

RDTSCP在您的 AMD Athlon 64 X2 上将提供时间戳计数器,其分辨率取决于您的时钟。但是精度与分辨率不同,您需要锁定线程关联并禁用中断(请参阅 IRQ 路由)。

这需要下降到汇编程序或使用MSVC 2008内在函数的 Windows 开发人员。

带有 RHEL5 的 RedHat 引入了gettimeofday用高分辨率RDTSCP调用替换的用户空间填充程序:

另外,请检查您的硬件,AMD 5200 的时钟为 2.6Ghz,时钟间隔为 0.4ns,其成本gettimeofdayRDTSCP221 个周期,最多等于 88ns。

于 2012-09-28T16:38:08.060 回答