在尝试构建一个对延迟非常敏感的应用程序时,需要每秒发送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时间戳。