c - 背对背rdtsc进行负时钟周期测量?

标签 c x86-64 inline-assembly overhead rdtsc

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

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

但是,这里最大的问题是,有时我得到的开销为负值(不一定是平均值,而至少是for循环内的部分值)。

这也会影响sem_wait()操作所需的cpu周期数的连续计算,有时甚至会为负数。如果我写的内容不清楚,这就是我正在处理的部分代码。

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



(编者注:有关获取完整的64位时间戳的正确且可移植的方法,请参见Get CPU cycle count? asm约束在为x86-64编译时仅会获得低32位或高32位,具体取决于寄存器分配恰好为"=A"输出选择了RAX或RDX。而不会选择uint64_t。)

(编辑器的第二个注释:哎呀,这就是为什么我们得到负面结果的答案。仍然值得在此处留下注释,以警告您不要复制此edx:eax实现。)



#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;
}

最佳答案

英特尔首次发明TSC时,它测量的是CPU周期。由于各种电源管理功能,“每秒循环数”不是恒定的;因此,TSC最初对衡量代码的性能有好处(而对测量经过的时间不利)。

不论好坏那时,CPU并没有真正进行过多的电源管理,无论如何,CPU通常以固定的“每秒循环数”运行。一些程序员有一个错误的想法,并误用了TSC来测量时间而不是周期。后来(当电源管理功能的使用变得越来越普遍时),这些人滥用TSC来测量时间时,他们就误解了由滥用引起的所有问题。 CPU制造商(从AMD开始)更改了TSC,因此它测量的是时间而不是周期(使之无法测量代码性能,但可以正确测量经过的时间)。这引起了混乱(软件很难确定TSC实际测量的是什么),因此稍后在AMD上,CPUID上添加了“ TSC Invariant”标志,因此,如果设置了此标志,程序员将知道TSC已损坏(用于测量)。周期)或固定(用于测量时间)。

英特尔遵循AMD并更改了TSC的行为以测量时间,并采用了AMD的“ TSC不变”标志。

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


TSC衡量时间和性能(每秒周期数不变)
TSC衡量绩效而不是时间
TSC测量时间而不是性能,但不使用“ TSC不变”标志来表示
TSC测量时间而不是性能,并且确实使用“ TSC不变”标志来表示(大多数现代CPU)


对于TSC测量时间的情况,要正确地测量性能/周期,您必须使用性能监视计数器。遗憾的是,性能监控计数器针对不同的CPU(特定于型号)有所不同,并且需要访问MSR(特权代码)。这使得应用程序无法测量“周期”。

还要注意,如果TSC确实测量了时间,您将无法知道它返回的时间标度(“假装周期”中有多少纳秒),而无需使用其他时间源来确定标度因子。

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

当然,不同的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指令,而放弃不可靠的测量)。然后从“测量某物”结果中减去测量的开销。这使您可以更好地估计实际“花费”的时间。

注意:如果您可以从《奔腾》首次发布时(1990年代中期-不确定它是否现在可以在线使用-我从1980年代开始就存档了)中提取《英特尔系统编程指南》的副本,您会发现英特尔记录了时间戳记计数器是“可以用来监视和识别处理器事件发生的相对时间”的东西。他们保证(不包括64位环绕)它会单调增加(但不会以固定速率增加),并且至少要花10年才能环绕。手册的最新版本详细记录了时间戳计数器,指出对于较旧的CPU(P6,Pentium M,较旧的Pentium 4),时间戳计数器“随每个内部处理器时钟周期而增加”,而“ Intel(r) SpeedStep(r)技术的过渡可能会影响处理器时钟。”以及较新的CPU(较新的Pentium 4,Core Solo,Core Duo,Core 2,Atom),TSC均以恒定速率递增(这就是“前进的体系结构行为”)。本质上,从一开始,它就是一个(变量)“内部周期计数器”用于时间戳记(而不是一个用于跟踪“墙上时钟”时间的时间计数器),并且这种行为在2000年(基于Pentium 4的发布日期)。

关于c - 背对背rdtsc进行负时钟周期测量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19941588/

相关文章:

c++ - 从文件对象到文件名

concurrency - x86_64 内存重新排序

c - 来自 x86_64 Linux 中内联 asm 的系统调用?

c++ - 将 __m256 的奇数元素提取到 __m128 中的有效(在 Ryzen 上)方法?

c++ - SPARC : How to handle integer doubleword pairs? 的 GCC 内联汇编

使用c复制文本文件

比较 c 中两个文件的数字;使用 fopen 等

c - libcurl (c api) READFUNCTION 用于 http PUT 永远阻塞

linux - x86_64 机器的 GCC 内联汇编的汇编程序错误(at&t 语法)

c - 内联汇编 block 中的临时寄存器