c-相当于gettimeofday的更快速度



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

POSIX时钟

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

  • 时间=>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_CUTIME_ID)
  • clock_gettime(ns)=>370个周期(clock_PROCESS_CPUTIME_ID)
  • rdtsc(周期)=>24个周期

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

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

TSC

访问TSC(处理器时间戳计数器)是最准确、最便宜的计时方式。一般来说,这就是内核本身所使用的。它在现代英特尔芯片上也很直接,因为TSC在内核之间同步,不受频率缩放的影响。因此,它提供了一个简单的全局时间源。您可以在此处看到一个使用它的示例,并在此处演练程序集代码。

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

Memcached

Memcached麻烦做缓存方法。它可能只是为了确保跨平台的性能更可预测,或者使用多个内核进行更好的扩展。它也可能不是一个有价值的优化。

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

以每秒100条消息的速度,每条消息的CPU时间为10ms。如果你有多个核心,假设它可以完全并行化,你可以很容易地将其增加4-6x,即每条消息40-60ms!获取每日时间的成本不太可能接近10ms——我怀疑它更像是1-10微秒(在我的系统上,微基准标记每次通话大约需要1微秒——自己试试吧)。您的优化工作最好花在其他地方。

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

也就是说,如果你愿意牺牲大量的分辨率(你的计时只会精确到最后一个刻度,这意味着它可能会关闭几十毫秒),你可以使用CLOCK_MONOTONIC_COARSE或带有CLOCK_gettime的CLOCK_REALTIME_COARSE。这也可以用vdso实现,并保证不会调用内核(对于最近的内核和glibc)。

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

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

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

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

但是,对于每秒数百条消息来说,这一切都太夸张了。

下面是一个基准。我看到大约30纳秒。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;
}

100000000个呼叫或30ns时为3秒;

2014-03-20.09:23:35
2014-03-20.09:23:38
2014-03-20.09:23:38
2014-03-20.09:23:41

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

相关内容

  • 没有找到相关文章

最新更新