【问题标题】:What's the best timing resolution can i get on Linux我可以在 Linux 上获得的最佳时序分辨率是多少
【发布时间】:2012-09-28 16:07:36
【问题描述】:

我正在尝试测量并行端口上 2 个信号之间的时间差,但首先我要知道我的测量系统(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 纳秒。我很高兴!

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

#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获得更好的分辨率??

【问题讨论】:

  • 当并行端口上的信号发生变化时如何通知您的程序?该代码路径有多长?信号和程序对它的了解之间的延迟有多可预测? (提示:最后两个问题的答案以 µs 为单位,而不是 ns。)
  • 正如我所解释的,首先我在我的测量机 xD 上做一些测试。但我基本上是在考虑用并行端口上的while循环检查替换代码中的NULL部分。
  • 你能扩展一下“在并行端口上的while循环检查”吗?你打算如何做到这一点?我无法想象有任何方法可以从 linux 用户模式进程中以亚微秒的精度进行检查。因此时钟的精度毫无意义。
  • 抱歉,我的回答如此基本,但事实是,此时我最关心的是了解时间测量限制并尽量减少它们。然后我可以做一些硬件循环来增加测量的时间间隔,或者可以想出其他想法来解决这个问题。我很感激你的建议,但如果你能告诉我你对我上面写的内容的看法:“489 ns 是我真正的分辨率”,我也很感激。
  • 在我的一台 PC(Intel Core2Duo T7500、Ubuntu 10.04)上,我得到如下数字:2584、2514、2444、2374、1607、1606、1536、1466。这似乎意味着我的源时钟以 70ns 运行。在另一台 PC(Core2Quad Q6600,Ubuntu 12.04)上,我得到 913、906、880、879、876、849。我不确定这意味着什么。我查看了计时器的内核源代码。它使用TSC 来计算高分辨率时间。

标签: c++ resolution timing


【解决方案1】:

我意识到这个话题早已死去,但想把我的发现扔进去。这是一个很长的答案,所以我把简短的答案放在这里,有耐心的人可以完成剩下的。这个问题的答案不完全是 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
显卡:英特尔® 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 微秒,那么您最好在感兴趣的代码中循环足够的次数,以便间隔更像是 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 的二阶差异时分辨率。

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

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
}

【讨论】:

  • 谢谢你。我们使用您的工具来决定我们基准机器的 CLOCK_MONOTONIC_RAW 的分辨率。大约是 53ns。奇怪的是 CLOCK_MONOTONIC_COARSE 几乎 100% 的时间显示分辨率为 0.0。也许与双重分辨率有关?您是否考虑过将其打包为准备就绪的工具?还有一条评论,使用大的-l 参数会扰乱程序。谢谢
【解决方案2】:

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

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

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

最后,如果您想用您的计算机替换示波器的功能,那么它在相对低频信号之外就无法工作。投资范围会更好——甚至是 a simple, portable, hand-held that plugs into your computer 用于显示数据。

【讨论】:

  • 感谢您的回复,但是如果纳秒范围不可用,那么为什么我们在 time.h 库中有 nanosleep 功能?对于硬件解决方案,您有什么建议?
  • 恐怕我不是就合适的范围寻求建议的最佳资源。试试电气工程论坛。
  • 不是我想要做的范围,更像是一个 GPS,计算两个信号之间的时间差。也称为到达时间 TOA 技术
  • GPS 单元是一种专门的硬件,它恰好需要一个高精度的时钟,因为它依赖于时间增量来计算位置 - 时钟越准确,定位就越准确。示波器是一种硬件,专为需要检查电线上的信号的人而设计。它的设计目的是能够非常准确地判断一个信号的持续时间,或者两个信号之间的时间差。
  • @MikyDinescu:GPS 接收器本身不需要时钟,它们从许多 GPS 卫星接收准确的时间,只需要建立差异。
【解决方案3】:

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

这需要使用MSVC 2008 instrinsics 下降到汇编程序或Windows 开发人员。

带有 RHEL5 的 RedHat 引入了用户空间 shim,将 gettimeofday 替换为高分辨率 RDTSCP 调用:

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

【讨论】:

  • 现在已经快四年了,那个 URL 已经死了。 :(
猜你喜欢
  • 2017-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-19
  • 2015-05-24
  • 1970-01-01
相关资源
最近更新 更多