纳秒级精度的时间测量

图片

几个月前,一个历史性时刻来到了我身边。 对我来说,用于测量时间的标准操作系统工具已远远不够。 花费时间进行纳秒级精度和纳秒级开销的测量。

我决定写一个库来解决这个问题。 乍一看,似乎没有什么特别的事情要做。 但是,像往常一样,仔细研究后发现,有许多有趣的问题需要解决。 在本文中,我将讨论这些问题以及如何解决这些问题。

由于您可以在计算机上测量许多不同类型的时间,因此我将立即澄清,在这里我们将讨论“秒表时间”。 或挂钟时间。 它是实时的,经过的时间等。 也就是说,这是一个简单的“人工”时间,我们在任务开始时检测到该时间,并在任务结束时停止。

微秒-几乎永远


过去几年中,高性能系统的开发人员已经习惯了微秒级的时间范围。 您可以在几微秒内从NVMe驱动器读取数据。 可以在几微秒内通过网络发送数据。 当然,不适合所有人,但适用于InifiniBand网络-轻松。

同时,微秒也具有结构。 完整的I / O堆栈由几个软件和硬件组件组成。 其中一些引入的延迟处于亚微秒级别。

为了测量这种大小的延迟,微秒精度不再足够。 但是,不仅精度很重要,而且测量时间的开销也很重要。 Linux clock_gettime()系统调用以纳秒精度返回时间。 在触手可及的机器上(2.60GHz的英特尔®至强®CPU E5-2630 v2 @),此调用大约需要120 ns的时间完成。 很好的身材。 此外,clock_gettime()的工作方式可预测。 这样一来,您就可以考虑通话的开销,并以数十纳秒量级的精度进行实际测量。 但是,现在让我们注意这一点。 要测量时间间隔,您需要进行两次这样的调用:在开始和结束时。 即 花费240 ns。 如果测量的间隔时间间隔约为1-10μs,则在某些情况下,测量过程本身将使观测到的过程明显变形。

我从最近几年IO堆栈如何加速开始了本节。 这是新的,但远非想要快速准确地测量时间的唯一原因。 这样的需求一直存在。 例如,总有一个代码要至少加速微处理器的1个时钟周期。 或来自原始文章中有关耸人听闻的Spectre漏洞的另一个示例:

图片

在这里,第72-74行测量单个存储器访问操作的执行时间。 的确,Spectre对纳秒不感兴趣。 时间可以用“鹦鹉”来衡量。 我们将回到鹦鹉和秒。

时间戳记计数器


快速准确的时间测量的关键是特殊的微处理器计数器。 该计数器的值通常存储在单独的寄存器中,并且通常(但并非总是)可以从用户空间访问。 在不同的体系结构上,计数器的调用方式有所不同:

  1. x86时间戳计数器
  2. PowerPC上的时基寄存器
  3. Itanium上的间隔时间计数器

在下面,我将始终使用名称“时间戳计数器”或TSC,尽管实际上无论结构如何,我都会牢记任何此类计数器。

一条指令通常即可(但并非总是如此)读取TSC值。 这是x86的示例。 严格来说,这不是纯汇编程序指令,而是GNU内联汇编程序:

uint32_t eax, edx; __asm__ __volatile__( "rdtsc" : "=a" (eax), "=d" (edx)); 

rdtsc指令将TSC寄存器的两个32位减半放置在eax和edx寄存器中。 其中,您可以“粘合”单个64位值。

我再次指出:在大多数情况下,此(和类似)指令可以直接从用户空间调用。 没有系统调用。 最小的开销。

现在需要测量时间吗?

  1. 在我们感兴趣的时间间隔开始时执行一条这样的指令。 记住柜台价值
  2. 最后执行一条这样的指令。 我们相信,从第一条指令到第二条指令的计数器的值将增加。 否则,为什么需要它? 记住第二个值
  3. 我们考虑两个存储值之间的差异。 这是我们的时间

看起来很简单,但是...

通过所述程序测量的时间以“鹦鹉”表示。 不是几秒钟。 但是有时候,鹦鹉正是您所需要的。 在某些情况下,时间间隔的绝对值并不重要,但是不同的时间间隔如何相互关联。 上面的Spectre示例完全演示了这种情况。 每个单独的内存访问的持续时间都没有关系。 唯一重要的是,对某些地址的调用将比对其他地址的执行快得多(取决于数据是存储在高速缓存还是主存储器中)。

但是如果不需要鹦鹉,又需要秒/微秒/纳秒等,该怎么办? 这里可以区分两种根本不同的情况:

  1. 纳秒级是必需的,但随后需要。 也就是说,允许首先在鹦鹉中进行所有必要的测量,然后将其存储在某个地方以进行进一步处理(例如,存储在内存中)。 并且只有在测量完成之后,才将收集到的鹦鹉缓慢地转换成几秒钟
  2. 纳秒级需要“即时”。 例如,您的测量过程中有您无法控制的某种“消费者”,并且期望以“人类”格式使用时间

第一种情况很简单,第二种情况-需要机智。 转换应尽可能有效。 如果消耗大量资源,则会极大地扭曲测量过程。 我们将在下面讨论有效转换。 到目前为止,我们已经在这里确定了这个问题,然后继续讨论另一个问题。

时间戳计数器并不是我们想要的那么简单。 在某些架构上:

  1. 不能保证TSC会高频更新。 例如,如果TSC每隔一微秒更新一次,则将无法修复十亿分之一秒。
  2. TSC更新的频率可能会随时间变化
  3. 在系统中存在的不同CPU上,可以以不同的频率更新TSC
  4. TSC在不同的CPU上滴答作响之间可能会有变化

这是说明最后一个问题的示例。 假设我们有一个带有两个CPU的系统:CPU1和CPU2。 假设第一个CPU上的TSC落后第二个CPU的滴答数,即等于5秒。 进一步假设在系统中启动了一个流来测量计算时间,他本人就是这样做的。 为此,流首先读取TSC值,然后进行计算,然后读取第二个TSC值。 如果线程在其整个生命周期中仅保留在一个CPU上(在任何CPU上),那么就没有问题。 但是,如果线程在CPU1上启动,在那里测量了第一个TSC值,然后在操作系统执行过程中将计算结果移至CPU2,并在其中读取第二个TSC值,该怎么办? 在这种情况下,计算似乎比实际时间长5秒。

由于上述问题,TSC在某些系统上不能用作可靠的时间来源。 但是,在其他遭受相同问题困扰的系统上,仍然可以使用TSC。 这要归功于特殊的建筑功能:

  1. 每当更新TSC的频率改变时,设备就会产生一个特殊的中断。 同时,设备还提供了查找当前频率的机会。 或者,可以将TSC刷新率置于操作系统的控制下(请参阅“ Power ISA版本2.06修订版B,第二册,第5章”)
  2. 设备还可以与TSC值一起提供在其上读取该值的CPU的ID(请参阅Intel RDTSCP指令,“ Intel 64和IA-32体系结构软件开发人员手册”,第2卷)
  3. 在某些系统上,您可以以编程方式调整每个CPU的TSC值(请参阅Intel WRMSR指令并注册IA32_TIME_STAMP_COUNTER,“ Intel 64和IA-32体系结构软件开发人员手册”,第3卷)

总的来说,如何在不同的体系结构上实现时间表是很有趣且广泛的。 如果您有时间和兴趣,我建议您潜水。 例如,您将会学到,有些系统使您可以通过编程方式确定TSC是否可以用作可靠的时间来源。

因此,有许多TSC的体系结构实现,每种都有自己的特点。 但有趣的是,所有动物园都已确立了总体趋势。 现代硬件和操作系统努力确保

  1. TSC在系统中的每个CPU上以相同的频率滴答
  2. 这个频率不会随时间变化
  3. 在不同CPU上滴答的TSC之间没有变化

在设计我的库时,我决定从这个前提出发,而不是从硬件实现的烦恼开始。

图书馆


我没有开始使用一堆不同架构的硬件芯片。 相反,我决定我的图书馆将以趋势为导向。 她纯粹出于经验重点:

  1. 它使您可以通过实验验证TSC作为时间源的可靠性
  2. 还可以让您实验性地计算将滴答声快速转换为纳秒所需的参数
  3. 自然地,该库提供了方便的接口来读取TSC并“即时”将滴答转换为纳秒。

库代码在这里可用。 它将仅在Linux上编译和执行。

在代码中,您可以看到所有方法的实现细节,稍后将进行讨论。

TSC可靠性评估


该库提供了一个返回两个评级的接口:

  1. 属于不同CPU的计数器之间的最大移位。 仅考虑可用于该进程的CPU。 例如,如果一个进程有三个可用的CPU,并且这些CPU上的TSC同时为50、150、20,则最大移位将为150-20 = 130。 自然地,该库将无法通过实验获得实际的最大位移,但是它将给出适合该位移的估计。 下一步做什么评估? 使用方法 这已经解决了客户端代码。 但是含义大致如下。 最大偏移量是客户端代码的尺寸可能失真的最大值。 假设在我们的示例中,使用三个CPU,客户端代码开始在CPU3(TSC为20)上测量时间,并在CPU2(TSC为150)上结束时间。 事实证明,额外的130个滴答声将爬入所测量的间隔。 再也不会。 CPU1和CPU2之间的差异只有100个滴答声。 估计有130个滴答声(实际上,它将更加保守),客户可以决定这种失真值是否适合他
  2. 在相同或不同CPU上顺序测量的TSC值是否增加。 这是主意。 假设我们有几个CPU。 假设他们的时钟是同步的并且在相同的频率上滴答作响。 然后,如果您首先在一个CPU上测量时间,然后在任何可用的CPU上再次测量时间,则第二个数字应大于第一个数字。

    我将这个估算值称为TSC单调性估算值以下

现在,让我们看一下如何获得第一个估计值:

  1. 该过程可用的处理器之一被声明为“基本”
  2. 然后对所有其他CPU进行排序,并为每个CPU计算移位: TSC___CPU – TSC___CPU 。 这样做如下:
    • a)依次获取三个测量值(一个接一个!): TSC_base_1, TSC_current, TSC_base_2 。 在这里,current表示该值是在当前CPU上测量的,并且基于
    • b)偏移TSC___CPU – TSC___CPU必须位于间隔[TSC_current – TSC_base_2, TSC_current – TSC_base_1] 。 这是基于TSC在两个CPU上以相同的频率滴答的假设。
    • c)步骤a)-b)重复几次。 计算在步骤b)中获得的所有间隔的交点。 结果间隔用作TSC___CPU – TSC___CPU偏移的估计值

  3. 在获得相对于基准的每个CPU的偏移估计之后,很容易获得所有可用CPU之间最大偏移的估计:
    • a)计算最小间隔,包括在步骤2中获得的所有结果间隔
    • b)该间隔的宽度被视为在不同CPU上滴答的TSC之间最大偏移的估计


为了评估库中的单调性,实现了以下算法:

  1. 假设一个进程有N个CPU
  2. 在CPU1上测量TSC
  3. 在CPU2上测量TSC
  4. ...
  5. 在CPUN上测量TSC
  6. 在CPU1上再次测量TSC
  7. 我们验证测量值从第一个到最后一个单调增加

在此重要的是,第一个和最后一个值是在同一CPU上测量的。 这就是为什么。 假设我们有3个CPU。 假设CPU2上的TSC相对于CPU1上的TSC偏移了+100个滴答。 还假设CPU3上的TSC相对于CPU2上的TSC偏移了+100个滴答。 考虑以下事件链:

  • 读取CPU1上的TSC。 取值为10
  • 通过了2个滴答声
  • 读取CPU2上的TSC。 必须为112
  • 通过了2个滴答声
  • 读取CPU3上的TSC。 必须为214

到目前为止,时钟看起来已经同步。 但是,让我们再次测量CPU1上的TSC:

  • 通过了2个滴答声
  • 读取CPU1上的TSC。 必须为16

糟糕! 单调被打破。 事实证明,在同一CPU上测量第一个和最后一个值可以使您检测到时钟之间或多或少的大偏移。 当然,下一个问题是:“转变幅度有多大?” 可以检测到的偏移量取决于连续TSC测量之间经过的时间。 在给定的示例中,这些仅是2个滴答。 超过2滴答的小时之间的变化将被检测到。 一般而言,将不会检测到小于连续测量之间经过的时间的偏移。 因此,时间越密集的测量结果越好。 两种估计的准确性都取决于此。 进行更密集的测量:

  • 最大偏移估计值越低
  • 对单调的评估更有信心

在下一节中,我们将讨论如何进行严格的测量。 我将在此处添加,在计算TSC可靠性估算值时,该库会执行许多更简单的“虱子”检查,例如:

  • 有限验证,以确保不同CPU上的TSC以相同的速度滴答
  • 检查计数器是否随时间变化,而不仅仅是显示相同的值

两种收集计数器值的方法


在该库中,我实现了两种用于收集TSC值的方法:

  1. 在CPU之间切换 。 在这种方法中,评估TSC可靠性所需的所有数据都是由一个线程从一个CPU“跳转”到另一个CPU收集的。 上一节中描述的两种算法均适用于此方法,而另一种则不适用。
    “在CPU之间切换”没有实际用途。 该方法只是为了“玩转”而实现的。 该方法的问题在于,将流从一个CPU“拖动”到另一个CPU所需的时间非常长。 因此,在连续的TSC测量之间需要花费大量时间,并且估计的准确性非常低。 例如,在23,000滴答的区域中获得了TSC之间最大偏移的典型估计。

    但是,该方法有两个优点:
    • 这是绝对确定性的。 如果需要一致地测量CPU1,CPU2,CPU3上的TSC,那么我们只需执行以下操作:切换到CPU1,读取TSC,切换到CPU2,读取TSC,最后,切换到CPU3,读取TSC
    • 据推测,如果系统中CPU的数量增长很快,那么它们之间的切换时间将增长得更慢。 因此,从理论上讲,显然可以存在一个系统-一个非常大的系统! -在其中使用该方法将是合理的。 但这还是不太可能

  2. 使用CAS订购的测量 。 在这种方法中,数据是由多个线程并行收集的。 每个可用的CPU启动一个线程。 使用“比较并交换”操作,将不同线程进行的测量按单个顺序排列。 下面是一段代码,显示了如何完成此操作。
    该方法的思想是从fio借来的, fio是一种生成I / O负载的流行工具。

    通过这种方法的强大功能获得的可靠性估计值已经相当不错。 例如,已经在几百个滴答声的水平上获得了最大偏移的估计。 通过检查单调性,您可以在数百个时钟周期内使时钟不同步。

    但是,上一节中给出的算法不适用于此方法。 对于他们而言,重要的是要以预定顺序测量TSC值。 “ CAS要求的测量”方法不允许这样做。 取而代之的是,首先收集一长串随机测量值,然后算法(已经有所不同)尝试查找以此顺序在“合适的” CPU上读取的值。

    在这里我将不给出这些算法,以免引起您的注意。 您可以在代码中看到它们。 有很多评论。 从理论上讲,这些算法是相同的。 一个根本上的新观点是验证随机类型的TSC序列在统计上如何“定性”。 还可以为TSC可靠性估计设置最低的统计意义可接受水平。

    理论上,在非常大型的系统上,“ CAS排序”方法可能会产生较差的结果。 该方法要求处理器竞争对公共存储器位置的访问。 如果有很多处理器,那么竞争可能会非常激烈。 结果,将难以创建具有良好统计特性的测量序列。 但是,目前看来,这种情况不太可能发生。

我答应了一些代码。 这就是使用CAS在单个链中构建测量结果的样子。

  for ( uint64_t i = 0; i < arg->probes_count; i++ ) { uint64_t seq_num = 0; uint64_t tsc_val = 0; do { __atomic_load( seq_counter, &seq_num, __ATOMIC_ACQUIRE); __sync_synchronize(); tsc_val = WTMLIB_GET_TSC(); } while ( !__atomic_compare_exchange_n( seq_counter, &seq_num, seq_num + 1, false, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)); arg->tsc_probes[i].seq_num = seq_num; arg->tsc_probes[i].tsc_val = tsc_val; } 

此代码在每个可用的CPU上执行。 所有线程都可以访问共享变量seq_counter 。 在读取TSC之前,流将读取此变量的值并将其存储在变量seq_num 。 然后读取TSC。 然后,它尝试以原子方式将seq_counter加1,但前提是变量的值自读取以来未更改。 如果操作成功,则意味着线程设法“分出”存储在seq_num中的测得TSC值后面的序列号。 下一个可以使用的序列号(可能已经在另一个线程中)将是另一个。 因为此数字来自seq_counter变量,所以每次成功调用__atomic_compare_exchange_n()将该变量加1。

__atomic与__sync ???
为了避免__atomic ,请注意,将__atomic系列的内置功能与过时的__sync系列的功能一起使用似乎很丑陋。 在代码中使用__sync_synchronize()可以避免将TSC读取操作与上游操作重新排序。 这需要一个完整的内存屏障。 __atomic族正式不具有具有相应属性的功能。 尽管实际上有: __atomic_signal_fence() 。 此函数使用在同一流上执行的信号处理程序来组织流计算。 实际上,这是一个完全的障碍。 但是,这没有明确说明。 而且我更喜欢没有隐藏语义的代码。 因此, __sync_synchronize()是一个完全停止的内存屏障。

这里值得一提的另一点是,所有涉及测量的流量或多或少同时开始。 我们对在不同CPU上读取的TSC值尽可能混合的事实感兴趣。 例如,当一个线程首先启动,完成其工作,然后所有其他线程才启动时,我们对这种情况不满意。 所得的TSC序列将具有无用的属性。 无法估算。 同时启动所有线程很重要-为此,已在库中采取了措施。

快速将刻度转换为纳秒


在检查了TSC的可靠性之后,该库的第二个主要目的是即时将滴答声转换为纳秒。 我从已经提到的Fio中借用了这种转换的想法。 但是,我必须进行一些重大改进,因为正如我的分析所显示的那样,转换程序本身在运行上还不够好。 在那里,您得到的准确性较低。

我将从一个例子开始。

理想情况下,我想像这样将刻度转换为纳秒:
ns_time = tsc_ticks / tsc_per_ns
我们希望花费在转换上的时间最少。 因此,我们旨在仅使用整数算法。 让我们看看这如何威胁我们。

如果tsc_per_ns = 3 ,那么从准确性的角度来看,简单的整数除法可以很好地工作: ns_time = tsc_ticks / 3

但是如果tsc_per_ns = 3.333怎么办?如果此数字舍入为3,则转换精度将非常低。为了如下解决这个问题:
ns_time = (tsc_ticks * factor) / (3.333 * factor)

如果因子factor足够大,则精度会很好。但是有些事情仍然会很糟糕。即,转换开销。整数除法是一项非常昂贵的操作。例如,在x86上,它需要10个以上的时钟周期。另外,整数除法运算并不总是流水线。

我们在等价形式重写我们的公式
ns_time = (tsc_ticks * factor / 3.333) / factor

第一次分裂不是问题。我们可以预先(factor / 3.333)- 预先计算。但是第二师仍然很痛苦。为了摆脱她,让我们选择factor等于二的幂。在那之后,第二个部分可以用移位代替-一种简单而快速的操作。

您可以选择多大factor?不幸的是,factor它不能任意大。它受以下条件的限制:分子中的乘法不应导致64位类型的溢出。是的,我们只想使用“本机”类型。同样,将转换开销保持在最低水平。

现在,让我们看看factor在我们的特定示例中它可以有多大。假设我们要以不超过一年的时间间隔工作。在这一年中,TSC tiknet时间如下:3.333 * 1000000000 * 60 * 60 * 24 * 365 = 105109488000000000。划分的64位类型数的最大值是:18446744073709551615 / 105109488000000000 ~ 175.5。所以表达(factor / 3.333)不应超过此值。然后我们有factor <= 175.5 * 3.333 ~ 584.9。不超过此数字的2的最大幂是512。因此,我们的转换公式采用以下形式:

ns_time = (tsc_ticks * 512 / 3.333) / 512

或:

ns_time = tsc_ticks * 153 / 512

精细。现在让我们看一下该公式的精确度。一年包含1000000000 * 60 * 60 * 24 * 365 = 31536000000000000纳秒。我们的公式给出:105109488000000000 * 153 / 512 = 31409671218750000。与当前值的差为126328781250000纳秒或126328781250000 / 1000000000 / 60 / 60 ~ 35小时。

这是一个大错误。我们想要更好的准确性。如果我们测量的时间间隔不超过一个小时怎么办?我将省略计算。它们与刚刚完成的完全相同。最终公式将是:

ns_time = tsc_ticks * 1258417 / 4194304(1)

转换错误每1小时只有119,305纳秒(小于0.2毫秒)。非常非常好如果最大可兑换值甚至不到一个小时,那么精度会更高。但是我们如何使用呢?不将时间测量限制为一小时吗?

要注意以下问题:

tsc_ticks = (tsc_ticks_per_1_hour * number_of_hours) + tsc_ticks_remainder

如果我们预先计算tsc_ticks_per_1_hour,我们可以提取number_of_hourstsc_ticks。接下来,我们知道一小时内包含多少纳秒。因此,对于我们来说,毫不费力地将tsc_ticks相当于整个小时数的那部分翻译成毫微秒。要完成转换,我们需要以毫微秒为单位进行翻译tsc_ticks_remainder。但是,我们知道,这种滴答声的发生时间不到一个小时。因此,要将其转换为纳秒,我们可以使用公式(1)。

做完了这种转换机制适合我们。让我们现在对其进行总结和优化。

首先,我们要灵活控制转换错误。我们不想将转换参数绑定到1小时的时间间隔。使其为任意时间间隔:

tsc_ticks = modulus * number_of_moduli_periods + tsc_ticks_remainder

再次,请记住如何将余数转换为纳秒:

ns_per_remainder = (tsc_ticks_remainder * factor / tsc_per_nsec) / factor

我们计算转换参数(我们知道tsc_ticks_remainder < modulus):

modulus * (factor / tsc_per_nsec) <= UINT64_MAX
factor <= (UINT64_MAX / modulus) * tsc_per_nsec
2 ^ shift <= (UINT64_MAX / modulus) * tsc_per_nsec


为避免繁琐,应注意,最后一个不等式不等于整数算术框架中的第一个不等式。但是,我不会对此进行详细介绍。我只能说最后一个不平等比第一次更严重,因此可以安全使用。

从最后一个不等式获得后shift,我们将进行计算:然后,将这些参数用于将余数转换为纳秒:因此,我们计算出了余数转换。接下来要解决的问题-是消除。与往常一样,我们希望尽快做到。与往常一样,我们不想使用除法。因此,我们只需选择等于2的幂:然后:

factor = 2 ^ shift
mult = factor / tsc_per_nsec




ns_per_remainder = (tsc_ticks_remainder * mult) >> shift


tsc_ticks_remaindernumber_of_moduli_periodstsc_ticksmodulus

modulus = 2 ^ remainder_bit_length



number_of_moduli_periods = tsc_ticks >> remainder_bit_length
tsc_ticks_remainder = tsc_ticks & (modulus - 1)


太好了现在我们知道如何从tsc_ticks number_of_moduli_periods和中提取tsc_ticks_remainder而且我们知道如何转换tsc_ticks_remainder为纳秒。仍有待了解如何将刻度的那部分刻度(几分之一秒)转换为纳秒modulus但是,一切都很简单:

ns_per_moduli = ns_per_modulus * number_of_moduli_periods

ns_per_modulus您可以预先计算。此外,根据相同的公式,我们将余数转换为相同的公式。此公式的使用时间不得超过modulusmodulus当然,他自己不超过modulus

ns_per_modulus = (modulus * mult) >> shift

仅此而已!我们能够快速计算出将滴答转换为纳秒所需的所有参数。现在简要总结一下转换过程:

  1. 我们有 tsc_ticks
  2. number_of_moduli_periods = tsc_ticks >> remainder_bit_length
  3. tsc_ticks_remainder = tsc_ticks & (modulus - 1)
  4. ns = ns_per_modulus * number_of_moduli_periods + (tsc_ticks_remainder * mult) >> shift

在此过程中,参数remainder_bit_lengthmodulus, ns_per_modulusmultshift预先计算提前。

如果您仍在阅读这篇文章,那么您很棒。您甚至有可能是性能分析师或高性能软件的开发人员。

所以在这里。事实证明我们还没有完成:)

还记得我们如何计算参数mult吗?就像这样:

mult = factor / tsc_per_nsec

问题:它来自tsc_per_nsec哪里?
一纳秒内的刻度数是一个非常小的值。实际上,是tsc_per_nsec使用了我的库(tsc_per_sec / 1000000000)那就是:

mult = factor * 1000000000 / tsc_per_sec

还有两个有趣的问题:

  1. 例如,为什么为什么tsc_per_sectsc_per_msec呢?
  2. 从哪里得到这些tsc_per_sec

让我们从第一个开始。 Fio现在实际上使用了每毫秒的滴答数。而且有问题。上机,其中的参数我上面命名tsc_per_msec = 2599998。一会儿tsc_per_sec = 2599998971。如果我们将这些数字统一化,那么它们的比率将非常接近于统一:0.999999626。但是,如果我们使用第一个而不是第二个,那么每秒钟我们将有374纳秒的误差。因此- tsc_per_sec

进一步...如何计算tsc_per_sec

这是在直接测量的基础上完成的:“某个时间”是可配置的参数。它可以更大,更小或等于一秒。假设是半秒钟。进一步假设的真正区别,并且是等于0.6秒。然后

start_sytem_time = clock_gettime()
start_tsc = WTMLIB_GET_TSC()
-
end_system_time = clock_gettime()
end_tsc = WTMLIB_GET_TSC()


end_system_timestart_system_timetsc_per_sec = (end_tsc – start_tsc) / 0,6

库以这种方式考虑了几个值tsc_per_sec。然后,使用标准方法,“清除”统计噪声,并接收tsc_per_sec可以信任的单个值

在上面的时间测量图中,呼叫clock_gettime()的顺序重要WTMLIB_GET_TSC()。重要的WTMLIB_GET_TSC()是,两次通话之间和两次通话之间要经过相同的时间clock_gettime()。这样就可以轻松地将系统时间与TSC滴答关联起来。然后,值的散布tsc_per_sec实际上可以被认为是随机的。使用此测量方案,这些值tsc_per_sec将以相同的概率在任一方向上偏离平均值。并且可以对它们应用标准的过滤方法。

结论


也许就这些。

但是有效时间测量的主题不限于此。有很多细微差别。对于那些感兴趣的人,我建议独立处理以下问题:

  • 将转换参数存储在缓存中,甚至更好地存储在寄存器中
  • 可以减少到什么限制modulus(从而提高转换精度)?
  • 如我们所见,转换的准确性不仅受modulus,而且受时间间隔的大小影响,该时间间隔与刻度(tsc_per_msectsc_per_sec)相关。如何平衡两个因素的影响?
  • 虚拟机中的TSC。我可以使用吗?
  • . , fio timespec. :

    tp->tv_sec = nsecs / 1000000000ULL;

    , TSC . , ,

本文讨论的方法使我们能够以数十纳秒量级的精度来测量一秒的时间刻度。这是我在使用库时实际观察到的准确性。

有趣的是,我借用了一些方法的fio在第二刻度上恰好损失了700-900纳秒(这有三个原因)。另外,由于以标准Linux格式存储时间,因此转换速度会降低。但是,我急于让粉丝们放心。我向开发人员介绍了我发现的所有转换问题人们已经在工作,他们会尽快修复它。

祝您有个愉快的纳秒!

Source: https://habr.com/ru/post/zh-CN425237/


All Articles