faster equivalent of gettimeofday
"gettimeofday"函数是用来获取当前时间的,对于发送几百个消息每秒的应用来说,它的速度已经足够快了。但是,如果每秒发送的消息数量达到了百万级别,情况可能会有所不同(但是你仍然需要先测量一下它是否成为了瓶颈)。在这种情况下,你可能需要考虑使用以下方法来提高速度:
1. 使用一个全局变量来保存当前的时间戳,精确到你所需要的级别。
2. 创建一个专用的后台线程,该线程只负责更新时间戳。例如,如果时间戳需要每隔T个时间单位更新一次,那么线程可以睡眠T的一部分时间,然后更新时间戳(如果需要的话,可以使用实时特性)。
3. 其他所有的线程(或者主进程,如果你没有使用其他线程)只需要读取全局变量即可。
C语言并不保证在时间戳的值大于"sig_atomic_t"时仍然可以读取时间戳的值。你可以使用锁来解决这个问题,但是锁的开销比较大。相反,你可以使用"volatile sig_atomic_t"类型的变量来索引一个时间戳数组:后台线程更新数组的下一个元素,然后更新索引。其他线程读取索引,然后读取数组:它们可能会得到一个稍微过时的时间戳(但是下一次会得到正确的),但是它们不会遇到在时间戳同时被更新时读取到部分旧值和部分新值的问题。
但是,对于每秒只有几百个消息的应用来说,这些方法都过于复杂了。
“创建一个专用的后台线程,该线程只负责更新时间戳(如果时间戳需要每隔T个时间单位更新一次” <- 这正是CLOCK_*_COARSE所做的事情,只不过专用线程实际上是一个中断处理程序,是系统级别的,而且内核人员已经为你处理了读取撕裂等问题 :)
我不确定这是否比Linux的"gettimeofday()"更快:每次写都有可能导致SMP上每个读取器都发生缓存失效。
想想看,Linux上的vvars是否是CPU本地的?如果是的话,那么CLOCK_*_COARSE就有另一个重大优势了...编辑:看起来不是(lxr.linux.no/linux+v2.6.39/arch/x86/kernel/vsyscall_64.c#L76),但是失效一个或两个缓存行比用本地计时器中断或IPI中断中断所有CPU要好吧。
Lars,这不是一个每秒多少次的问题,应用程序希望尽快构建一条消息并将其发送给接收方,而且还要与其他发送方竞争。这是一个交易应用,所以无论频率是多低还是多高,我们都希望尽可能地节省微秒数。
问题的出现原因是对于高性能计时需求,gettimeofday
函数的执行速度较慢。在每秒100条消息的情况下,每条消息的CPU时间为10毫秒。如果有多个核心,假设可以完全并行化,可以轻松将这个时间增加4-6倍,即每条消息的时间为40-60毫秒。gettimeofday
的开销不太可能达到10毫秒,我怀疑它更像是1-10微秒(在我的系统上进行微基准测试每次调用大约需要1微秒-自行尝试)。优化工作最好放在其他地方进行。
解决方法之一是使用时间戳计数器(TSC),但是现代Linux已经有了基于TSC的用户空间实现的gettimeofday
函数-如果可能,vdso将引入一个gettimeofday
的实现,该实现会将一个偏移量(从共享的内核用户内存段中读取)应用于rdtsc
的值,从而计算出当前时间,而无需进入内核。然而,一些CPU模型在不同核心或不同处理包之间没有同步的TSC,因此这个功能可能会被禁用。如果要获得高性能的计时功能,可以首先考虑寻找具有同步TSC的CPU模型。
另一种解决方法是使用CLOCK_MONOTONIC_COARSE
或CLOCK_REALTIME_COARSE
与clock_gettime
函数,虽然会牺牲一定的分辨率(计时只能精确到最后一个滴答,可能会有几十毫秒的误差),但它也是通过vdso实现的,并且保证不会调用内核(对于较新的内核和glibc)。
每个进程都是单线程的,服务器通常会运行10-20个这样的进程。
对于"具有同步TSC的CPU模型",可以使用Xeon 5680进行研究,检查dmesg中的"Marking TSC unstable"。如果存在这个信息,则表示没有使用TSC。但是,无论如何,在尝试优化之前,一定要进行基准测试。不仅因为你不知道它是否足够快,而且如果不进行基准测试,你将永远不知道是否有所改进。
dmesg | grep TSC
命令显示Fast TSC calibration using PIT
。如果只看到这个信息,那么可能正在使用TSC。但是,仍然要对gettimeofday()
进行基准测试。它的执行速度是否足够快?
对于gettimeofday()
,每次调用需要大约178个周期,因此每次调用大约需要0.06微秒。这比我的快得多(我的笔记本电脑实际上有一个不稳定的TSC,并会调用内核来使用HPET)-希望178个周期对您来说足够快?
我们的基准测试显示gettimeofday()
每次调用需要7微秒,而clock_gettime()
每次调用需要9微秒。然而,正如你所说,gettimeofday()
的计时可能会有高达10毫秒的错误,我认为即使它慢2微秒,也是可以接受的。
关于clock_gettime()
为什么比gettimeofday()
精度更低,什么是"计时只能精确到最后一个滴答"的意思,这是一个相关的问题。之所以这个问题重要,是因为在我的系统上Qt使用clock_gettime()
来实现定时器事件,可能是因为它是单调的而不是系统时间。为什么clock_gettime()
不能像gettimeofday()
一样精确?
在这篇文章中,作者针对POSIX时钟源进行了基准测试,并给出了各种时钟方法的运行时间。作者使用TSC寄存器进行循环计数,并在Intel Core i7-4771 CPU @ 3.50GHz上运行每种时钟方法数千次,取最小的耗时值作为测试结果。
作者提到,TSC是访问处理器时间戳计数器的最准确和最便宜的方式,它提供了一个简单的全局时间源。不过,TSC的问题是无法直接将循环周期转换为纳秒。作者发现,Intel文档中提到TSC以固定频率运行,但这个频率可能与处理器的标称频率不同,而Intel并没有提供可靠的方法来确定TSC频率。Linux内核解决这个问题的方式是通过测试两个硬件计时器之间发生的TSC周期数。
在讨论中,某些情况下了使用缓存方法的问题。作者解释说,这可能是为了确保性能在不同平台上更可预测,或者在多核情况下更好地扩展。但是,也有人认为这可能没有必要进行优化。
另外,文章中还提到了一些关于时钟方法的讨论。有人指出,在测试时取最小值是没有意义的,因为重复调用会使缓存和分支预测器预热。还有人指出,在实际使用中,这些调用很少被调用,缓存很可能是冷的。还某些情况下了Linux内核的一些优化措施,比如通过VDSO页面从内存中加载值来读取时间。
文章主要讨论了POSIX时钟源的基准测试结果以及使用TSC寄存器进行循环计数的优劣势。作者提到了TSC无法直接转换为纳秒的问题,并介绍了Linux内核的一些解决方法。此外,还讨论了缓存方法的优化问题以及一些关于时钟方法的讨论。