27

在尝试构建一个对延迟非常敏感的应用程序时,它需要每秒发送 100 条消息,每条消息都有时间字段,我们想考虑优化 gettimeofday。首先想到的是rdtsc基于优化。有什么想法吗 ?还有其他指针吗?返回的时间值所需的精度以毫秒为单位,但如果该值偶尔与接收器不同步 1-2 毫秒,这并不是什么大问题。尝试比 62 纳秒 gettimeofday 做得更好

4

5 回答 5

63

POSIX 时钟

我为 POSIX 时钟源编写了一个基准测试:

  • 时间 (s) => 3 个周期
  • ftime (ms) => 54 个周期
  • gettimeofday (us) => 42 个周期
  • clock_gettime (ns) => 9 个周期 (CLOCK_MONOTONIC_COARSE)
  • clock_gettime (ns) => 9 个周期 (CLOCK_REALTIME_COARSE)
  • clock_gettime (ns) => 42 个周期 (CLOCK_MONOTONIC)
  • clock_gettime (ns) => 42 个周期 (CLOCK_REALTIME)
  • clock_gettime (ns) => 173 个周期 (CLOCK_MONOTONIC_RAW)
  • clock_gettime (ns) => 179 个周期 (CLOCK_BOOTTIME)
  • clock_gettime (ns) => 349 个周期 (CLOCK_THREAD_CPUTIME_ID)
  • clock_gettime (ns) => 370 个周期 (CLOCK_PROCESS_CPUTIME_ID)
  • rdtsc(周期)=> 24 个周期

这些数字来自 Linux 4.0 上的 Intel Core i7-4771 CPU @ 3.50GHz。这些测量是使用 TSC 寄存器进行的,每个时钟方法运行数千次,并采用最小成本值。

您需要在您打算运行的机器上进行测试,尽管这些机器的实现方式因硬件和内核版本而异。代码可以在这里找到。它依赖于 TSC 寄存器进行循环计数,该寄存器位于同一个 repo ( tsc.h ) 中。

TSC

访问 TSC(处理器时间戳计数器)是最准确和最便宜的计时方式。通常,这是内核自己使用的。它在现代英特尔芯片上也非常简单,因为 TSC 跨内核同步并且不受频率缩放的影响。因此它提供了一个简单的全局时间源。您可以在此处查看使用它的示例以及此处的汇编代码演练。

这个(除了可移植性)的主要问题是似乎没有一个从周期到纳秒的好方法。据我所知,英特尔文档表明 TSC 以固定频率运行,但该频率可能与处理器规定的频率不同。英特尔似乎没有提供一种可靠的方法来计算 TSC 频率。Linux 内核似乎通过测试两个硬件计时器之间发生了多少个 TSC 周期来解决这个问题(参见此处)。

内存缓存

Memcached 费心去做缓存方法。可能只是为了确保跨平台的性能更可预测,或者使用多个内核更好地扩展。它也可能不是值得的优化。

于 2012-10-27T03:14:51.070 回答
49

您是否真的进行了基准测试,发现gettimeofday速度慢得令人无法接受?

以每秒 100 条消息的速率,每条消息有 10 毫秒的 CPU 时间。如果您有多个内核,假设它可以完全并行化,您可以轻松地将其增加 4-6 倍——即每条消息 40-60 毫秒!gettimeofday 的成本不太可能接近 10 毫秒——我怀疑它更像是 1-10 微秒(在我的系统上,微基准测试每次调用大约需要 1 微秒——你自己试试吧)。您的优化工作最好花在其他地方。

虽然使用 TSC 是一个合理的想法,但现代 Linux 已经有一个基于用户空间 TSC 的 gettimeofday - 在可能的情况下,vdso 将引入一个 gettimeofday 的实现,它将偏移量(从共享的内核用户内存段读取)应用于rdtsc's值,从而在不进入内核的情况下计算一天中的时间。但是,某些 CPU 型号没有在不同内核或不同封装之间同步的 TSC,因此最终可能会被禁用。如果您想要高性能时序,您可能首先要考虑寻找具有同步 TSC 的 CPU 型号。

也就是说,如果您愿意牺牲大量的分辨率(您的时间只会精确到最后一个滴答声,这意味着它可能会偏离数十毫秒),您可以将CLOCK_MONOTONIC_COARSE 或 CLOCK_REALTIME_COARSEclock_gettime一起使用。这也通过 vdso 实现,并保证不会调用内核(对于最近的内核和 glibc)。

于 2011-06-27T21:18:17.157 回答
4

就像 bdonian 说的那样,如果你每秒只发送几百条消息,gettimeofday那就足够快了。

但是,如果您每秒发送数百万条消息,它可能会有所不同(但您仍然应该衡量它是一个瓶颈)。在这种情况下,您可能需要考虑这样的事情:

  • 有一个全局变量,以您期望的精度给出当前时间戳
  • 有一个专门的后台线程,除了更新时间戳之外什么都不做(如果时间戳应该每 T 单位时间更新,那么让线程休眠 T 的一部分,然后更新时间戳;如果需要,请使用实时功能)
  • 所有其他线程(或主进程,如果您不使用其他线程)只是读取全局变量

C 语言不保证时间戳值大于sig_atomic_t. 您可以使用锁定来解决这个问题,但锁定很重。相反,您可以使用volatile sig_atomic_t类型化变量来索引时间戳数组:后台线程更新数组中的下一个元素,然后更新索引。其他线程读取索引,然后读取数组:它们可能会得到一点点过时的时间戳(但下次它们会得到正确的时间戳),但它们不会遇到读取时间戳的问题同时它正在被更新,并得到一些旧值的字节和一些新值。

但是对于每秒数百条消息来说,所有这些都太过分了。

于 2011-06-27T21:43:45.097 回答
1

下面是一个基准。我看到大约 30ns。rashad 的 printTime()如何在 C++ 中获取当前时间和日期?

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

void printTime(time_t now)
{
    struct tm  tstruct;
    char       buf[80];
    tstruct = *localtime(&now);
    strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &tstruct);
    cout << buf << endl;
}

int main()
{
   timeval tv;
   time_t tm;

   gettimeofday(&tv,NULL);
   printTime((time_t)tv.tv_sec);
   for(int i=0; i<100000000; i++)
        gettimeofday(&tv,NULL);
   gettimeofday(&tv,NULL);
   printTime((time_t)tv.tv_sec);

   printTime(time(NULL));
   for(int i=0; i<100000000; i++)
        tm=time(NULL);
   printTime(time(NULL));

   return 0;
}

100,000,000 次调用需要 3 秒或 30ns;

2014-03-20.09:23:35
2014-03-20.09:23:38
2014-03-20.09:23:38
2014-03-20.09:23:41
于 2014-03-20T13:30:00.507 回答
0

你需要毫秒精度吗?如果不是,您可以简单地使用time()和处理 unix 时间戳。

于 2011-06-27T21:10:13.620 回答