跨CPU核心的rdtsc精度

38
我正在一个线程中发送网络数据包,并在运行在不同CPU核心上的第二个线程中接收回复。我的进程测量每个数据包的发送和接收之间的时间(类似于ping)。我使用rdtsc获取高分辨率,低开销的计时,这是我实现所需的。
所有的测量看起来都很可靠。但是,我还是担心rdtsc在不同核心之间的准确性,因为我读到一些文本暗示tsc在核心之间没有同步。
我在维基百科上找到了以下关于TSC的信息:
“恒定的TSC行为确保每个时钟周期的持续时间是均匀的,并支持即使处理器核心更改频率也可以将TSC用作墙时钟计时器。这是未来所有英特尔处理器的架构行为。”
尽管如此,我仍然担心跨核心的精度问题,这就是我的问题。
更多信息:
我在Intel Nehalem机器上运行我的进程。
操作系统是Linux。
对于所有核心,设置了“constant_tsc”CPU标志。

你考虑过使用HPET吗? - Aaron Klotz
我之前不知道HPET。刚刚了解到它似乎是一种高精度定时器(基于中断而非时钟)。我需要能够在需要时读取高分辨率时钟(例如:在网络数据包到达时)。 - avner
6个回答

36

X86_FEATURE_CONSTANT_TSCX86_FEATURE_NONSTOP_TSC位于cpuid中(edx=x80000007,第8位;请查看linux内核的 unsynchronized_tsc函数进行更多检查)。

在英特尔设计人员手册vol3b的16.11.1不变TSC一节中,它说:

"16.11.1不变TSC

较新处理器中的时间戳计数器可能支持一种增强功能,称为不变TSC。CPUID.80000007H:EDX[8]指示处理器对不变TSC的支持。

不变TSC将以所有ACPI P、C和T状态下的恒定速率运行。这是向前移动的架构行为。在支持不变TSC的处理器上,操作系统可以使用TSC作为墙钟计时器服务(而不是ACPI或HPET计时器)。TSC读取更有效率,不会产生环路转换或访问平台资源所带来的开销。"

因此,如果TSC可用于墙钟,则保证它们同步。


5
@avner,可以通过使用简单的2线程测试来检查CPU核心/ CPU套件之间的tsc变化,该测试使用共享变量进行“乒乓”并忙等待事件(无互斥锁,仅读取/写入;还包含rdtsc读取)。当线程固定到不同的核心时,它们将给出tsc0-tsc1。然后以相反的顺序设置affinity以获取tsc1-tsc0。如果两者相等,则具有同步TSC。 - osgx
谢谢你的回答osgX。你的回答听起来非常有趣。我找到了一个等效的答案http://www.gossamer-threads.com/lists/xen/devel/185419。从中我了解到constant_tsc + nonstop_tsc与invariant(跨处理器)等价,但需要对BIOS / mobo做出更多假设。我的旧测试显示CPU之间没有漂移 - 我只关心未来的测试和在客户现场工作的保证。总之,有了你的答案,我比以前更有信心;因此,我会接受这个答案并希望一切顺利 :-) - avner
@avner,一些现代CPU可能没有“不变TSC”功能,必须进行检查。 - osgx
4
请注意:使用此标志,虽然保证tsc在多个处理器核心上是一致的,但系统可能配备了多个CPU,因此仍需谨慎。 - Suma
1
@Suma,这个答案的推理是,文档中说你可以使用rdtsc来计算步行时钟时间,这意味着你必须能够确信它在多个核之间同步。如果这个推理成立,那么它是否也适用于CPU之间呢? - Joseph Garvin

4
在最新的处理器上,您可以在同一包(即仅有一个Core iX处理器的系统)的不同核心之间执行此操作,但是您无法在不同的包(处理器)之间执行此操作,因为它们不会共享rtc。您可以通过cpu亲和性(将相关线程锁定到特定核心)来解决这个问题,但这又取决于您的应用程序的行为方式。
在Linux上,您可以在/proc/cpuinfo中检查constant_tsc以查看处理器是否具有适用于整个包的单个tsc。原始寄存器位于CPUID.80000007H:EDX [8]
根据我所读到的,但尚未通过编程进行确认的是,从11h修订版起,AMD CPU对于此cpuid位具有相同的含义。

1
IRQ平衡与此有何关系? - Joseph Garvin
1
@JosephGarvin 绝对没有什么问题,rdtsc 是紧密耦合的。我可能喝了太多咖啡 - 或者在想完全不同的事情,但是这些年过去后很难记得。不过你发现得真好,我会进行编辑。 - Alexandre Pereira Nunes

3
实际上,核心似乎不共享TSC,请查看此线程:http://software.intel.com/en-us/forums/topic/388964 总之,不同的核心不共享TSC,有时如果一个核心切换到特定的能量状态,TSC可能会失去同步,但这取决于CPU的类型,因此您需要检查英特尔文档。似乎大多数操作系统在启动时同步TSC。
我使用一台装有i5处理器的Linux Debian机器,通过激发-反应算法检查了不同核心之间的TSC差异。激发进程(在一个核心中)将TSC写入共享变量,当反应进程检测到该变量的更改时,它将比较其值并将其与自己的TSC进行比较。这是我的测试程序的示例输出:
TSC ping-pong test result:
TSC cores (exciter-reactor): 0-1
100 records, avrg: 159, range: 105-269
Dispersion: 13
TSC ping-pong test result:
TSC cores (exciter-reactor): 1-0
100 records, avrg: 167, range: 125-410
Dispersion: 13

当激励器CPU为0时(平均为159个节拍),反应时间几乎与激励器CPU为1时相同(167个节拍)。 这表明它们非常好地同步了(可能有几个节拍的差异)。 在其他核心对中,结果非常相似。另一方面,rdtscp汇编指令返回一个值,指示TSC所读取的CPU。虽然这不是您的情况,但当您想在简单的代码段中测量时间并确保在代码中间未将进程移动到CPU时,它会很有用。

4
虽然这个链接可能回答了问题,但最好在这里包含答案的关键部分,并提供链接作为参考。仅包含链接的答案可能会因为链接页面的更改而失效。 - Matthew Green
1
@MatthewGreen 我扩展了我的答案,并附上了我自己研究的一些结果。我保留了之前的网址,因为它仍然有用,而且不太可能失效。 - Will

2

在Linux中,您可以使用clock_gettime(3)与CLOCK_MONOTONIC_RAW一起使用,这将为您提供纳秒级分辨率,并且不受ntp更新的影响(如果有任何发生)。


2
谢谢,但还不够好。在我的环境中未定义CLOCK_MONOTONIC_RAW。我已经尝试了time.h中的CLOCK_MONOTONIC。虽然struct timespec具有纳秒分辨率,但是当调用CLOCK_MONOTONIC的clock_gettime时,最后3位数字始终具有相同的值;因此,实际上只有微秒分辨率。 - avner
4
以下是您的代码输出的前5行(您可以看到nsec始终为246) 279595 629885246 279596 630958246 279597 631777246 279598 633596246clock_gettime存在额外问题,即其开销较大。根据我的统计数据(在没有sleep的情况下重复时钟检索1001次),在一台强力的nehalem机器上,使用clock_gettime(CLOCK_MONOTONIC, &ts)的平均开销为281纳秒;而在同一台机器上,使用rdtsc只需要8纳秒。 - avner
2
我的问题是关于rdtsc的,因为我需要高分辨率和低开销。我只想确保它在所有核心上的可靠性,因为我找不到相关文档。虽然我感觉还不错。此外,为了使用高分辨率计时器(可能是CLOCK_MONOTONIC_HR),我需要重新编译内核。但这不是一个选项,因为我不能要求所有客户都这样做。 - avner
1
我已经设置了CPU亲和性 :) 我的问题是关于在两个不同的CPU核心上运行的2个线程。我的接收线程轮询NIC以获取数据包,而无需进行上下文切换并且无需延迟发送出站数据包。在我的环境中,微秒计数非常重要! - avner
上次我检查的时候,与gettimeofday或CLOCK_REALTIME不同,CLOCK_MONOTONIC_RAW没有快速的系统调用实现。 - Yu Zhou
显示剩余3条评论

1

您可以使用sched_set_affinity() API设置线程亲和性,以便在一个CPU核心上运行您的线程。


我已经设置了CPU亲和性 :( 我的问题是关于2个线程在2个不同的CPU核心上。我的接收线程轮询NIC以获取数据包,无需上下文切换并且无需延迟发送出站数据包。在我的环境中,微秒计数很重要! - avner
HEPT对我的需求来说不太好 - 请参考我之前关于HEPT的评论。在多核环境中,RDTSC看起来非常出色和可靠(即使是运行了多周的机器),我进行了数百次测试。此外,请阅读有关“TSC作为挂钟计时器”的引文。总之,我只需要正式批准。实际上,RDTSC似乎能够胜任工作。 - avner
核心之间的漂移可能会发生(数百毫秒)。 - Dima

0

我建议您不要使用rdtsc。它不仅不可移植,而且不可靠,通常不起作用 - 在某些系统上,rdtsc不会统一更新(例如如果您正在使用speedstep等)。如果您想要准确的时间信息,应该在套接字上设置SO_TIMESTAMP选项,并使用recvmsg()获取带有(微秒分辨率)时间戳的消息。

此外,使用SO_TIMESTAMP获得的时间戳实际上是内核收到数据包的时间,而不是您的任务注意到的时间。


5
谢谢您的回答。请注意,使用constant_tsc标志时,rdtsc会以均匀方式进行更新;请参见我在问题中添加的引用。 SO_TIMESTAMP精度为毫秒级别,而rdtsc精度为纳秒级别,这正是我所需要的精度。 我对数据包到达内核的时间不感兴趣,而是对用户获得它的时间感兴趣,因为这是我的应用程序加速的部分。 - avner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接