我在 Linux 上可以获得的最佳时序分辨率是多少

use*_*660 2 c++ resolution timing

我正在尝试测量并行端口上 2 个信号之间的时间差,但首先我要知道我的测量系统(AMD Athlon(tm) 64 X2 双核处理器 5200+ \xc3\x97 2) 在 SUSE 12.1 x64 上。

\n\n

因此,经过一番阅读后,我决定使用clock_gettime(),首先我使用以下代码获取clock_getres()值:

\n\n
/*\n * This program prints out the clock resolution.\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\nint main( void )\n  {\n    struct timespec res;\n\n    if ( clock_getres( CLOCK_REALTIME, &res) == -1 ) {\n      perror( "clock get resolution" );\n      return EXIT_FAILURE;\n    }\n    printf( "Resolution is %ld nano seconds.\\n",\n          res.tv_nsec);\n    return EXIT_SUCCESS;\n  }\n
Run Code Online (Sandbox Code Playgroud)\n\n

结果是:1 纳秒。我很高兴!

\n\n

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

\n\n
#include <iostream>\n#include <time.h>\nusing namespace std;\n\ntimespec diff(timespec start, timespec end);\n\nint main()\n{\n    timespec time1, time2, time3,time4;\n    int temp;\n    time3.tv_sec=0;\n    time4.tv_nsec=000000001L;\n    clock_gettime(CLOCK_REALTIME, &time1);\n        NULL;\n    clock_gettime(CLOCK_REALTIME, &time2);\n    cout<<diff(time1,time2).tv_sec<<":"<<diff(time1,time2).tv_nsec<<endl;\n    return 0;\n}\n\ntimespec diff(timespec start, timespec end)\n{\n    timespec temp;\n    if ((end.tv_nsec-start.tv_nsec)<0) {\n        temp.tv_sec = end.tv_sec-start.tv_sec-1;\n        temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec;\n    } else {\n        temp.tv_sec = end.tv_sec-start.tv_sec;\n        temp.tv_nsec = end.tv_nsec-start.tv_nsec;\n    }\n    return temp;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这个计算了两次调用clock_gettime之间的时间,time3和time4被声明但在这个例子中没有使用,因为我正在用它们做测试。

\n\n

本例中的输出在 978 到 1467 ns 之间波动。这两个数字都是 489 的倍数,这让我认为 489 ns 是我真正的分辨率。与上面获得的 1 ns 相差甚远。

\n\n

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

\n\n

我的项目确实需要至少 10 纳秒的分辨率。快点!GPS 可以获得比 PC 更好的分辨率?

\n

小智 6

我意识到这个话题早已消亡,但想提出我的发现。这是一个很长的答案,所以我把简短的答案放在这里,有耐心的人可以费力地完成其余的部分。这个问题的不完全答案是 700 ns 或 1500 ns,具体取决于您使用的clock_gettime() 模式。长答案要复杂得多。

\n

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

\n

硬件:
\nRAM:2.0 GiB // 这是 Ubuntu 在“系统设置”\xe2\x86\x92“详细信息”中的报告方式
\n处理器:Intel\xc2\xae Pentium(R) Dual CPU T2330 @ 1.60GHz \xc3\x97 2
\n显卡:Intel\xc2\xae 965GM x86/MMX/SSE2

\n

我想在即将到来的项目中准确地测量时间,并且作为 PC 硬件的相对新手,无论操作系统如何,我想我应该对计时硬件的分辨率进行一些实验。我偶然发现了这个问题。

\n

由于这个问题,我认为clock_gettime()看起来满足我的需求。但我过去使用 PC 硬件的经验让我不知所措,因此我开始进行一些实验,看看计时器的实际分辨率是多少。

\n

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

\n

结果摘要稍长:

\n
    \n
  1. 并不是真正的结果。结构中场的规定分辨率以纳秒为单位。调用clock_getres() 的结果也是tv_sec 0、tv_nsec 1。但是以前的经验告诉我们不要仅仅相信结构的分辨率。这是精度的上限,而现实往往要复杂得多。
  2. \n
  3. 在我的机器上,使用我的程序、使用我的操作系统,在某一特定的一天等,对于模式 0 和 1 来说,clock_gettime() 结果的实际分辨率是 70 纳秒。70 纳秒还不错,但不幸的是,这是正如我们将在下一点中看到的那样,这是不现实的。更复杂的是,使用模式 2 和 3 时,分辨率似乎为 7 ns。
  4. \n
  5. 对于模式 0 和 1,clock_gettime() 调用的持续时间更像是 1500 ns。如果需要 20 倍的分辨率才能获取值,那么声称时间分辨率为 70 ns 对我来说根本没有意义。
  6. \n
  7. Clock_gettime() 的某些模式比其他模式更快。模式 2 和 3 显然大约是模式 0 和 1 挂钟时间的一半。模式 0 和 1 在统计上无法区分。模式 2 和 3 比模式 0 和 1 快得多,其中模式 3 总体上是最快的。
  8. \n
\n

在继续之前,我最好定义模式:哪个模式是哪个?:
\nMode 0 CLOCK_REALTIME // 参考:http://linux.die.net/man/3/clock_gettime
\nMode 1 CLOCK_MONOTONIC
\nMode 2 CLOCK_PROCESS_CPUTIME_ID
\nMode 3 CLOCK_THREAD_CPUTIME_ID

\n

结论:对我来说,如果分辨率小于函数获取时间间隔所需的时间长度,那么谈论时间间隔的分辨率是没有意义的。例如,如果我们使用模式 3,我们知道该函数 99% 的时间都会在 700 纳秒内完成。而且我们还知道,我们返回的时间间隔将是 7 纳秒的倍数。因此,7 纳秒的“分辨率”是调用获取时间的时间的 1/100。我没有看到 7 纳秒的变化间隔有任何价值。对于分辨率问题有 3 种不同的答案:1 ns、7 或 70 ns,最后是 700 或 1500 ns。我赞成最后一个数字。

\n

总而言之,如果您想测量某些操作的性能,您需要记住clock_gettime()调用需要\xe2\x80\x93多长时间,即700或1500 ns。例如,尝试测量需要 7 纳秒的东西是没有意义的。为了便于讨论,假设您愿意接受性能测试结论中 1% 的错误。如果使用模式 3(我想我将在我的项目中使用),您将不得不说您需要测量的间隔需要是 700 纳秒或 70 微秒的 100 倍。否则你的结论会有超过1%的误差。因此,继续测量您感兴趣的代码,但如果您在感兴趣的代码中运行的时间少于 70 微秒,那么您最好循环遍历感兴趣的代码足够多次,以便间隔更像 70 微秒或更多。

\n

这些主张的理由和一些细节:

\n

先索赔3。这很简单。只需大量运行clock_gettime()并将结果记录在数组中,然后处理结果即可。在循环外进行处理,以使clock_gettime() 调用之间的时间尽可能短。

\n

这一切意味着什么?参见附图。例如,对于模式 0,大多数情况下对clock_gettime() 的调用花费的时间不到 1.5 微秒。可以看到模式0和模式1基本是一样的。然而,模式 2 和 3 与模式 0 和 1 有很大不同,并且彼此之间也略有不同。与模式 0 和 1 相比,模式 2 和 3 花费的 Clock_gettime() 挂钟时间大约是模式 0 和 1 的一半。另请注意,模式 0 和 1 与模式 2 和 3 不同,\xe2\x80\x93 彼此略有不同。模式 0 和模式 1 相差 70 纳秒 \xe2\x80\x93,这是一个我们将在权利要求 2 中讨论的数字。

\n

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

\n

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

\n

声明 2:如果您仔细观察准备图表时收集的样本,您很快就会注意到差异之间的差异(即二阶差异)在 70 纳秒左右(在模式 0 和模式之前)相对恒定 \xe2\x80\x93至少 1 个)。要重复此实验,请像以前一样收集“n”个时钟时间样本。然后计算每个样本之间的差异。现在将差异按顺序排序(例如 sort -g),然后导出各个独特的差异(例如 uniq -c)。

\n

例如:

\n
$ ./Exp03 -l 1001 -m 0 -k | sort -g | awk -f mergeTime2.awk | awk -f percentages.awk | sort -g\n1.118e-06 8 8 0.8 0.8       // time,count,cumulative count, count%, cumulative count%\n1.188e-06 17 25 1.7 2.5\n1.257e-06 9 34 0.9 3.4\n1.327e-06 570 604 57 60.4\n1.397e-06 301 905 30.1 90.5\n1.467e-06 53 958 5.3 95.8\n1.537e-06 26 984 2.6 98.4\n<snip>\n
Run Code Online (Sandbox Code Playgroud)\n

第一列中的持续时间之间的差异通常是 7e-8 或 70 纳秒。通过处理差异可以使这一点变得更加清晰:

\n
$ <as above> | awk -f differences.awk \n7e-08\n6.9e-08\n7e-08\n7e-08\n7e-08\n7e-08\n6.9e-08\n7e-08\n2.1e-07 // 3 lots of 7e-08\n<snip>\n
Run Code Online (Sandbox Code Playgroud)\n

请注意所有差异都是 70 纳秒的整数倍吗?或者至少在 70 纳秒的舍入误差内。

\n

这个结果很可能取决于硬件,但我实际上不知道目前是什么将其限制为 70 纳秒。也许某处有 14.28 MHz 振荡器?

\n

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

\n

相关代码(附后):

\n

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

\n

./Expo03 -l 100001 -m 3

\n

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

\n

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

\n

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

\n

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

\n

案例中附有percentages.awk 和differences.awk。

\n

停止媒体:我无法发布图表,因为我没有“至少 10 的声誉”。对此感到抱歉。

\n

罗布·沃森\n2014 年 11 月 21 日

\n

世博03.cpp

\n
/* Like Exp02.cpp except that here I am experimenting with\n   modes other than CLOCK_REALTIME\n   RW 20 Nov 2014\n*/\n\n/* Added CPU affinity to see if that had any bearing on the results\n   RW 21 Nov 2014\n*/\n\n#include <iostream>\nusing namespace std;\n#include <iomanip>\n\n#include <stdlib.h> // getopts needs both of these\n#include <unistd.h>\n\n#include <errno.h> // errno\n\n#include <string.h> // strerror()\n\n#include <assert.h>\n\n// #define MODE CLOCK_REALTIME\n// #define MODE CLOCK_MONOTONIC\n// #define MODE CLOCK_PROCESS_CPUTIME_ID\n// #define MODE CLOCK_THREAD_CPUTIME_ID\n\nint main(int argc, char ** argv)\n{\n  int NumberOf = 1000;\n  int Mode = 0;\n  int Verbose = 0;\n  int c;\n  // l loops, m mode, h help, v verbose, k masK\n\n\n  int rc;\n  cpu_set_t mask;\n  int doMaskOperation = 0;\n\n  while ((c = getopt (argc, argv, "l:m:hkv")) != -1)\n  {\n    switch (c)\n      {\n      case \'l\': // ell not one\n        NumberOf = atoi(optarg);\n        break;\n      case \'m\':\n        Mode = atoi(optarg);\n        break;\n      case \'h\':\n        cout << "Usage: <command> -l <int> -m <mode>" << endl\n             << "where -l represents the number of loops and "\n             << "-m represents the mode 0..3 inclusive" << endl\n             << "0 is CLOCK_REALTIME" << endl\n             << "1 CLOCK_MONOTONIC" <<  endl\n             << "2 CLOCK_PROCESS_CPUTIME_ID" << endl\n             << "3 CLOCK_THREAD_CPUTIME_ID" << endl;\n        break;\n      case \'v\':\n        Verbose = 1;\n        break;\n      case \'k\': // masK - sorry! Already using \'m\'...\n        doMaskOperation = 1;\n        break;\n      case \'?\':\n        cerr << "XXX unimplemented! Sorry..." << endl;\n        break;\n      default:\n        abort();\n      }\n  }\n\n  if (doMaskOperation)\n  {\n    if (Verbose)\n    {\n      cout << "Setting CPU mask to CPU 0 only!" << endl;\n    }\n    CPU_ZERO(&mask);\n    CPU_SET(0,&mask);\n    assert((rc = sched_setaffinity(0,sizeof(mask),&mask))==0);\n  }\n\n  if (Verbose) {\n    cout << "Verbose: Mode in use: " << Mode << endl;\n  }\n\n  if (Verbose)\n  {\n    rc = sched_getaffinity(0,sizeof(mask),&mask);\n    // cout << "getaffinity rc is " << rc << endl;\n    // cout << "getaffinity mask is " << mask << endl;\n    int numOfCPUs = CPU_COUNT(&mask);\n    cout << "Number of CPU\'s is " << numOfCPUs << endl;\n    for (int i=0;i<sizeof(mask);++i) // sizeof(mask) is 128 RW 21 Nov 2014\n    {\n      if (CPU_ISSET(i,&mask))\n      {\n        cout << "CPU " << i << " is set" << endl;\n      }\n      //cout << "CPU " << i \n      //     << " is " << (CPU_ISSET(i,&mask) ? "set " : "not set ") << endl;\n    }\n  }\n\n  clockid_t cpuClockID;\n  int err = clock_getcpuclockid(0,&cpuClockID);\n  if (Verbose)\n  {\n    cout << "Verbose: clock_getcpuclockid(0) returned err " << err << endl;\n    cout << "Verbose: clock_getcpuclockid(0) returned cpuClockID " \n       << cpuClockID << endl;\n  }\n\n  timespec timeNumber[NumberOf];\n  for (int i=0;i<NumberOf;++i)\n  {\n    err = clock_gettime(Mode, &timeNumber[i]);\n    if (err != 0) {\n      int errSave = errno;\n      cerr << "errno is " << errSave \n           << " NumberOf is " << NumberOf << endl;\n      cerr << strerror(errSave) << endl;\n      cerr << "Aborting due to this error" << endl;\n      abort();\n    }\n  }\n\n  for (int i=0;i<NumberOf-1;++i)\n  {\n    cout << timeNumber[i+1].tv_sec - timeNumber[i].tv_sec\n            + (timeNumber[i+1].tv_nsec - timeNumber[i].tv_nsec) / 1000000000.\n         << endl;\n    \n  }\n  return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

合并时间2.awk

\n
BEGIN {\n PROCINFO["sorted_in"] = "@ind_num_asc"\n}\n\n{array[$0]++}\n\nEND {\n  lastX = -1;\n  first = 1;\n  \n  for (x in array)\n  {\n    if (first) { \n      first = 0 \n      lastX = x; lastCount = array[x]; \n    } else {\n      delta = x - lastX;\n      if (delta < 2e-9) { # this is nasty floating point stuff!!\n        lastCount += array[x]; \n        lastX = x\n      } else {\n        Cumulative += lastCount;\n        print lastX "\\t" lastCount "\\t" Cumulative\n        lastX = x; \n        lastCount = array[x]; \n      }\n    }\n  }\n  print lastX "\\t" lastCount "\\t" Cumulative+lastCount\n}\n
Run Code Online (Sandbox Code Playgroud)\n

百分比.awk

\n
{ # input is $1 a time interval $2 an observed frequency (i.e. count)\n  # $3 is a cumulative frequency\n  b[$1]=$2;\n  c[$1]=$3;\n  sum=sum+$2\n} \n\nEND {\n  for (i in b) print i,b[i],c[i],(b[i]/sum)*100, (c[i]*100/sum);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

差异.awk

\n
NR==1 {\n  old=$1;next\n} \n{\n  print $1-old;\n  old=$1\n}\n
Run Code Online (Sandbox Code Playgroud)\n