使用背靠背rdtsc进行负时钟周期测量?

Dis*_*lus 17 c x86-64 overhead inline-assembly rdtsc

我正在编写一个C代码,用于测量获取信号量所需的时钟周期数.我正在使用rdtsc,在对信号量进行测量之前,我连续两次调用rdtsc来测量开销.我在for循环中重复了这么多次,然后我将平均值用作rdtsc开销.

这是正确的,首先要使用平均值吗?

尽管如此,这里的一个大问题是,有时我会得到开销的负值(不一定是平均值,但至少是for循环中的部分值).

这也影响了连续计算sem_wait()操作所需的cpu周期数,有时也证明是负数.如果我写的不清楚,这里有一部分我正在编写的代码.

为什么我会得到这样的负值?


(编者注:请参阅获取CPU周期计数?以获得完整的64位时间戳的正确和可移植方式."=A"编译为x86-64时,asm约束只能得到低或高32位,具体取决于寄存器分配是否发生为uint64_t输出选择RAX或RDX .它不会选择edx:eax.)

(编辑的第二个注释:哎呀,这就是为什么我们得到负面结果的答案.仍然值得留下一个注释作为警告,不要复制这个rdtsc实现.)


#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>

static inline uint64_t get_cycles()
{
  uint64_t t;
           // editor's note: "=A" is unsafe for this in x86-64
  __asm volatile ("rdtsc" : "=A"(t));
  return t;
}

int num_measures = 10;

int main ()
{
   int i, value, res1, res2;
   uint64_t c1, c2;
   int tsccost, tot, a;

   tot=0;    

   for(i=0; i<num_measures; i++)
   {    
      c1 = get_cycles();
      c2 = get_cycles();

      tsccost=(int)(c2-c1);


      if(tsccost<0)
      {
         printf("####  ERROR!!!   ");
         printf("rdtsc took %d clock cycles\n", tsccost);
         return 1;
      }   
      tot = tot+tsccost;
   }

   tsccost=tot/num_measures;
   printf("rdtsc takes on average: %d clock cycles\n", tsccost);      

   return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

Bre*_*dan 53

当英特尔首次发明TSC时,它测量了CPU周期.由于各种电源管理功能,"每秒周期数"不是恒定的; 所以TSC最初很适合测量代码的性能(并且不利于测量时间).

无论好坏; 那时CPU并没有真正有太多的电源管理,无论如何CPU往往以固定的"每秒周期"运行.一些程序员错误地想法并误用了TSC来测量时间而不是周期.后来(当电源管理功能的使用变得越来越普遍时),这些人滥用TSC来测量他们滥用造成的所有问题的时间.CPU制造商(从AMD开始)改变了TSC,因此它测量时间而不是周期(使其在测量代码性能时被破坏,但对于测量时间的测量是正确的).这引起了混淆(软件很难确定TSC实际测量的是什么),所以稍后AMD就在CPUID上添加了"TSC Invariant"标志,这样如果设置了这个标志,程序员就会知道TSC坏了(用于测量)循环)或固定(用于测量时间).

英特尔跟随AMD并改变了他们的TSC行为以测量时间,并采用了AMD的"TSC Invariant"标志.

这给出了4种不同的情况:

  • TSC测量时间和性能(每秒周期数不变)

  • TSC衡量的是绩效而非时间

  • TSC测量时间而不是性能,但不使用"TSC Invariant"标志来表示

  • TSC测量时间而不是性能,并使用"TSC Invariant"标志来表示(大多数现代CPU)

对于TSC测量时间的情况,要正确测量性能/周期,您必须使用性能监控计数器.遗憾的是,性能监视计数器对于不同的CPU(特定于模型)是不同的,并且需要访问MSR(特权代码).这使得应用程序测量"循环"非常不切实际.

另请注意,如果TSC确实测量时间,则无法知道它返回的时间范围("假装周期"中有多少纳秒),而不使用其他时间源来确定缩放因子.

第二个问题是,对于多CPU系统,大多数操作系统都很糟糕.操作系统处理TSC的正确方法是防止应用程序直接使用它(通过TSD在CR4中设置标志;以便RDTSC指令导致异常).这可以防止各种安全漏洞(定时侧通道).它还允许操作系统模拟TSC并确保它返回正确的结果.例如,当应用程序使用RDTSC指令并导致异常时,OS的异常处理程序可以确定要返回的正确"全局时间戳".

当然,不同的CPU都有自己的TSC.这意味着如果应用程序直接使用TSC,则它们会在不同的CPU上获得不同的值.帮助人们解决操作系统无法解决问题的方法(通过仿效RDTSC); AMD添加了RDTSCP指令,返回TSC和"处理器ID"(英特尔也最终采用了该RDTSCP指令).在损坏的操作系统上运行的应用程序可以使用"处理器ID"来检测它们何时在上一次运行在不同的CPU上; 并且以这种方式(使用RDTSCP指令),他们可以知道"逝去= TSC - previous_TSC"何时给出有效结果.然而; 此指令返回的"处理器ID"只是MSR中的一个值,操作系统必须将每个CPU上的该值设置为不同的值 - 否则RDTSCP将说明所有CPU上的"处理器ID"为零.

基本上; 如果CPU支持该RDTSCP指令,并且OS已正确设置"处理器ID"(使用MSR); 然后该RDTSCP指令可以帮助应用程序知道他们何时获得了"经过时间"错误的结果(但它无法提供修复或避免不良结果).

所以; 长话短说,如果你想要一个准确的性能测量,你大多是搞砸了.您真正希望的最好的是准确的时间测量; 但仅限于某些情况下(例如,在单CPU机器上运行或"固定"到特定CPU;或者RDTSCP在操作系统上使用时,只要您检测并丢弃无效值,就可以正确设置它).

当然,即使这样,你也会因为像IRQ这样的东西而得到狡猾的测量.为此原因; 最好在循环中多次运行代码并丢弃任何比其他结果高得多的结果.

最后,如果你真的想要正确地做,你应该衡量测量的开销.要做到这一点,你需要测量什么都不做的时间(仅仅是RDTSC/RDTSCP指令,同时丢弃不正确的测量值); 然后从"测量某事"结果中减去测量的开销.这可以让您更好地估计实际需要的时间.

注意:如果您可以从Pentium首次发布时(20世纪90年代中期 - 不确定它是否已经在线提供 - 我自20世纪80年代以来已经存档)您可以查阅英特尔系统编程指南的副本,您会发现英特尔记录了时间戳计数器"可用于监视和识别处理器事件发生的相对时间".他们保证(不包括64位环绕)它会单调增加(但不会以固定的速率增加)并且它需要至少10年才能完成.该手册的最新版本更详细地记录了时间戳计数器,表明对于较旧的CPU(P6,Pentium M,较旧的Pentium 4),时间戳计数器"随每个内部处理器时钟周期递增"和"Intel(r) SpeedStep(r)技术转换可能会影响处理器时钟"; 而较新的CPU(较新的Pentium 4,Core Solo,Core Duo,Core 2,Atom)TSC以恒定速率递增(这就是"架构行为向前发展").从本质上讲,它从一开始就是一个(可变的)"内部循环计数器"用于时间戳(而不是用于跟踪"挂钟"时间的时间计数器),并且这种行为在2000年(基于奔腾4发布日期).

  • 我用不同的方式说出来:AMD和英特尔意识到高精度低开销时间源比循环计数器更有用.在现代CPU中,硬件性能计数器可以做到这一点,因此您不需要`rdtsc`.并且您可以测量微基准测试的周期以外的事件.还要注意,一些早期的恒定速率TSC CPU在运行`hlt`指令时停止了TSC,使其无法用作时间源.(Linux的/ proc/cpuinfo显示没有这个问题的CPU的`nonstop_tsc`,以及固定速率功能的`constant_tsc`.) (2认同)

Spe*_*tre 6

  1. 不要使用平均值

    使用最小的一个或平均较小的值(由于CACHE而得到平均值),因为较大的值已被OS多任务中断.

    您还可以记住所有值,然后找到操作系统进程粒度边界并过滤掉此边界后的所有值(通常> 1ms很容易检测到)

    在此输入图像描述

  2. 无需衡量开销 RDTSC

    你只需要在一段时间内进行测量,并且在两个时间内都存在相同的偏移量,并且在减法后它就会消失.

  3. 用于可变时钟源RDTS(如在笔记本电脑上)

    您应该通过一些稳定的密集计算循环将CPU的速度更改为最大值,通常只需几秒即可.你应该连续测量CPU频率,并在它足够稳定时开始测量你的东西.


jth*_*ill 0

面对热量和空闲节流、鼠标运动和网络流量中断、无论 GPU 正在做什么,以及现代多核系统可以在没有任何人关心的情况下吸收的所有其他开销,我认为您唯一合理的做法是积累几千个单独的样本,然后在取中位数或平均值之前扔掉异常值(不是统计学家,但我敢说这在这里不会有太大区别)。

我认为,你为消除正在运行的系统的噪音所做的任何事情都会比仅仅接受你无法可靠地预测现在完成任何事情需要多长时间更严重地扭曲结果