faster equivalent of gettimeofday
在尝试构建一个对延迟敏感的应用程序时,需要每秒发送100条消息,每条消息都有时间字段,我们要考虑优化gettimeofday。
首先想到的是基于rdtsc的优化。 有什么想法吗 ? 还有其他指针吗?
返回的时间值所需的准确度以毫秒为单位,但如果该值偶尔与接收器不同步1-2毫秒,则不是很大。
试图比62纳秒的gettimeofday做得更好
-
时间需要准确到什么粒度?
-
请记住,时间戳计数器可能无法跨CPU同步,具体取决于CPU型号。此外,现代Linux将尽可能在rdtsc的用户空间中实现gettimeofday
-
你确定gettimeofday()有问题吗?你在使用哪种操作系统?在Linux,IIRC上,它被移动到用户空间(到vsyscall页面,或vDSO,不记得哪个),以允许它扩展到大量的CPU(由SGI的Christoph Lameter,IIRC完成)。
-
vsyscall有一个gettimeofday,但是vsyscall已经过时了,它的gettimeofday现在只是一个调用内核的存根。
-
@David毫秒
-
@bdonlan有什么办法可以确定吗?
-
@Humble,为什么不测量gettimeofday()所需的时间以确定它是否足够快?它需要多快?
-
我想添加一个YES的数据点,gettimeofday显然很慢。目前添加实时ETA例程到一个问题我必须暴力十亿次迭代嵌套for循环...在我的i3机器上,只是做一些计数器增量偶尔的状态调用我得到78M操作/秒;一个gettimeofday变为28M,其中两个(对于start =和end =)变为16M。
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_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的
Memcached很难做到缓存方法。它可能只是为了确保跨平台的性能更具可预测性,或者使用多个内核进行更好的扩展。它也可能不值得进行优化。
-
在你的github链接上你有相同的结果,但是在几纳秒内,与你在这里写的因子1000不同。
-
对不起,固定的时间表示法。
-
你的cached_clock方法只是在另一个线程中隐藏了测量所消耗的时间。另外,与其他方法相比,它的准确性还远远不够。请注意,如果您的主循环顺利且current_time == 2,则每次循环时都会增加2毫秒。
-
@Gull_Code当然,它隐藏了另一个线程的成本,说0ns根本不准确。它的"成本"实际上取决于您的应用。重点是你每个周期都要花费一次你所关心的粒度,而不是每次你需要时间。是的,准确性并不高,但再一次取决于用例。对于像memcached这样的东西,它非常好。对于paxos实现它可能很糟糕。
-
你怎么能以纳秒精度进行基准测试?有没有办法确保您的程序是唯一执行的程序,并且不允许上下文切换?
-
@Lundin你运行了很多轮 - 足够在上下文切换因素的情况下。
-
@haneefmubarak我的评论实际上是一个修辞问题,暗示除非程序在具有实时性能的系统上运行,否则这一切都没有任何意义。
-
我认为取最小值是无稽之谈。缓存,分支预测器将被重复调用预热。但是,当你在实际使用中调用这些调用时,很可能很少会调用它们,在这种情况下,缓存很可能很冷。 (关于阅读时间)。
-
我不确定time在你的Haswell上只需要3个时钟周期是否合理。或者它可能是从内存中读取共享计数器而不是使用rdtsc?如您所见,rdtsc在Haswell上的每24个周期的吞吐量为1。 (agner.org/optimize)。您是否检查了编译器生成的asm以确保它没有优化掉一些工作?
-
我刚检查过,time(NULL)确实跳转(通过PLT)到VDSO页面中的代码,该代码只是从内存中加载一个值。 mov rax,QWORD PTR [rip+0xffffffffffffc1fd] # 0x7ffff7ff70a8。代码来自arch/x86/entry/vdso/vclock_gettime.c中的__vdso_time()。如果这可以内联到movabs并避免PLT +调用+分支开销,我们将以每时钟2个吞吐量来读取1秒分辨率的时间:P
-
@Peter - clock_gettime(CLOCK_MONOTONIC_COARSE)也"比rdtsc更快",并且还从VDSO中的存储器位置读取。它做了一些数学计算,但它最终比time()更昂贵,但有时更有用,因为它具有更高的分辨率。遗憾的是它甚至不是更快,尽管你可以随时使用更新共享内存位置的周期性信号(或休眠线程)"滚动自己" - 然后你真的可以让你的1 uop读取高( ish)分辨率时钟。
您是否真的进行过基准测试,发现gettimeofday速度慢得令人无法接受?
以每秒100条消息的速率,每条消息有10毫秒的CPU时间。如果你有多个核心,假设它可以完全并行化,你可以轻松地将其增加4-6倍 - 每条消息40-60毫秒! gettimeofday的成本不太可能接近10毫秒 - 我怀疑它更像1-10微秒(在我的系统上,微基准测试它每次通话大约1微秒 - 自己试试)。您的优化工作将更好地用于其他地方。
虽然使用TSC是一个合理的想法,现代Linux已经有一个基于TSC的用户空间gettimeofday - 在可能的情况下,vdso将引入gettimeofday的实现,该实现将偏移量(从共享内核用户内存段读取)应用于的价值,因此计算一天中没有进入内核的时间。但是,某些CPU型号没有在不同内核或不同软件包之间同步的TSC,因此最终可能会被禁用。如果您需要高性能计时,您可能首先要考虑查找具有同步TSC的CPU模型。
也就是说,如果你愿意牺牲大量的分辨率(你的时间只能精确到最后一个滴答,意味着它可以关闭几十毫秒),你可以使用CLOCK_MONOTONIC_COARSE或CLOCK_REALTIME_COARSE和clock_gettime。这也是用vdso实现的,并且保证不会调用内核(对于最近的内核和glibc)。
-
每个进程都是单线程的。服务器通常会运行10-20个这样的进程。
-
"具有同步TSC的CPU模型",拥有Xeon 5680,将研究它对此的处理
-
@Humble,检查你的dmesg中的"标记TSC不稳定"。如果它在那里,你就不会使用TSC。但总是在尝试优化之前始终是基准测试。你不仅不知道它是否足够快开始,如果你没有基准,你永远不会知道你是否有所改善......
-
@bdonlan dmesg | grep TSC说Fast TSC calibration using PIT
-
有可能,如果这就是你所看到的,你就是在使用TSC。但仍然是基准gettimeofday()。它足够快吗?
-
为gettimeofday()获得大约178个周期,因此每个呼叫大约0.06微秒。
-
@Humble,嗯,这比我的快得多(我的笔记本电脑,它有一个不稳定的TSC并调用内核使用HPET) - 希望178个周期对你来说足够快? :)
-
我们的基准测试显示gettimeofday()每次调用7us,clock_gettime()每次调用9us。但是,正如你所说,gettimeofday()可能会给错误的时间提供10ms,我认为接受gettimeofday()是合理的,即使它2us更慢
-
@Viet我觉得你有一个错字
-
@bdonlan你的"自己检查"程序不能在clang上编译(所以我不能在OS X上运行它)
-
@bdonlan你能否扩展为什么clock_gettime()的准确性明显低于gettimeofday()?什么是"准确到最后一刻"?这是相关的原因是因为在我的系统上Qt使用clock_gettime()来实现定时器事件,大概是因为它是单调的而不是挂起时间。为什么clock_gettime()不能像gettimeofday()一样准确?
就像bdonian所说,如果你每秒只发送几百条消息,那么gettimeofday就足够快了。
但是,如果您每秒发送数百万条消息,则可能会有所不同(但您仍应测量它是瓶颈)。在这种情况下,您可能需要考虑这样的事情:
-
有一个全局变量,给出你所需精度的当前时间戳
-
有一个专用的后台线程除了更新时间戳之外什么都不做(如果时间戳应该每T个单位更新一次,那么让线程休眠一小部分T然后更新时间戳;如果需要,使用实时功能)
-
所有其他线程(或主进程,如果您不使用其他线程)只读取全局变量
如果C语言大于sig_atomic_t,则C语言不保证您可以读取时间戳值。您可以使用锁定来处理它,但锁定很重。相反,您可以使用volatile sig_atomic_t类型变量来索引时间戳数组:后台线程更新数组中的下一个元素,然后更新索引。其他线程读取索引,然后读取数组:它们可能会得到一点点过时的时间戳(但下次它们会得到正确的时间戳),但它们不会遇到问题,因为它们会读取时间戳。同时它正在更新,并获得旧值的一些字节和一些新值。
但是这一切对于每秒数百条消息来说太过分了。
-
"有一个专用的后台线程,除了更新时间戳之外什么都不做(如果时间戳应该每T个时间单位更新一次"< - 这正是CLOCK _ * _ COARSE所做的,除了专用线程实际上是一个中断处理程序并且是系统 - 广泛的,内核人已经处理了阅读撕裂和其他问题:)
-
我不确定它会比Linux的gettimeofday()更快:每次写入都可能导致SMP上每个读取器的缓存未命中。
-
想想看,Linux上有vvars cpu-local吗?如果是这样,这是CLOCK _ * _ COARSE ...的另一个主要优点:编辑:看起来不是(lxr.linux.no/linux+v2.6.39/arch/x86/kernel/vsyscall_64.c#L76),但是使缓存无效或者两行比使用本地定时器中断或IPI中断所有CPU更好
-
Lars,这不是一秒钟的问题,应用程序想要构建消息并尽快将其发送给接收者,并且正在与其他发送者竞争。这是一个交易应用程序,因此在接收器的每条消息中,无论我们想要削减微秒的频率有多低或多高。
-
感谢您的回答。会试一试。
以下是基准。我看到约30ns。来自rashad的printTime()如何在C ++中获取当前时间和日期?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #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秒或30ns的3秒;
1 2 3 4
| 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时间戳。
-
time()和gettimeofday()的比较,60纳秒与62纳秒。不多,需要做得更好。
-
也许有一个帖子:global_unix_ts = time(); sleep 500ms;。全局var甚至不受互斥锁的保护。这应该是快速照明。 bdonlan的答案似乎也非常优雅和完整。