基于CPU周期计算的C/C++ Linux x86_64中的分析

Hum*_*ger 5 c cpu profiling x86-64 cpu-usage

我使用以下代码来分析我的操作,以优化我的函数中的cpu周期.

static __inline__ unsigned long GetCC(void)
{
  unsigned a, d; 
  asm volatile("rdtsc" : "=a" (a), "=d" (d)); 
  return ((unsigned long)a) | (((unsigned long)d) << 32); 
}
Run Code Online (Sandbox Code Playgroud)

我不认为这是最好的,因为即使连续两次通话也给了我"33"的差异.有什么建议 ?

Olo*_*ell 6

我个人认为rdtsc指令很棒,可用于各种任务.我不认为使用cpuid是准备rdtsc所必需的.以下是我对rdtsc的理解:

  1. 由于我使用Watcom编译器,我使用"#pragma aux"实现了rdtsc,这意味着C编译器将生成内联指令,期望edx:eax中的结果,并通知其优化器eax和edx的内容已经改性.这是传统的_asm实现的一个巨大改进,优化器将远离在_asm附近进行优化.我还使用"#pragma aux"实现了divide_U8_by_U4,这样当我将clock_cycles转换为us或ms时,我就不需要调用lib函数.
  2. rdtsc的每次执行都会导致一些开销(如果在作者的示例中封装,则会更多),必须更多地考虑测量序列的时间.一般来说,我没有比内部时钟频率的1/30更短的时间序列,这通常可以达到1/10 ^ 8秒(3 GHZ内部时钟).我使用这种测量作为指示,而不是事实.知道了这一点,我可以省去cpuid.我衡量的次数越多,我就越接近事实.
  3. 为了可靠地测量,我将使用1/100 - 1/300范围i/e 0.03 - 0.1 us.在此范围内,使用cpuid的额外准确性实际上是微不足道的.我将此范围用于短序列计时.这是我的"非标准"单元,因为它取决于CPU的内部时钟频率.例如,在1 GHz机器上,我不会使用0.03 us,因为这会使我超出1/100限制,我的读数将成为指示.在这里,我将使用0.1 us作为最短时间测量单位.不会使用1/300,因为它太接近1 us(见下文)以产生任何显着差异.
  4. 对于更长的处理序列,我将两个rdtsc读数之间的差值除以3000(对于3 GHz)并将经过的时钟周期转换为我们.实际上我使用(diff + 1500)/ 3000,其中1500是3000的一半.对于I/O等待,我使用毫秒=>(diff + 1500000)/ 3000000.这些是我的"标准"单位.我很少使用秒.
  5. 有时我会得到意想不到的缓慢结果然后我必须问自己:这是由于中断还是代码?我测量了几次,看看它是否确实是一个中断.在那种情况下......好的中断在现实世界中一直发生.如果我的序列很短,那么下一次测量很可能不会被中断.如果序列较长,则会更频繁地发生中断,并且我无能为力.
  6. 非常准确地测量经过的时间(我们或更低的时间和更长的ET)将增加在divide_U8_by_U4中获得除法异常的风险,因此我想到何时使用我们以及何时使用ms.
  7. 我也有基本统计的代码.使用这个我记录最小值和最大值,我可以计算平均值和标准偏差.这段代码非常重要,因此必须从测量的ET中减去自己的ET.
  8. 如果编译器正在进行广泛的优化并且您的读数存储在局部变量中,则编译器可以确定("正确地")可以省略代码.避免这种情况的一种方法是将结果存储在公共(非静态,非基于堆栈)变量中.
  9. 在真实条件下运行的程序应该在真实条件下进行测量,没有办法解决这个问题.

至于时间戳计数器准确的问题我会说假设不同核心上的tsc是同步的(这是常态),在低活动期间存在CPU节流的问题以减少能量消耗.测试时始终可以禁用功能.如果您在同一处理器上执行1 GHz或10 Mhz的指令,则经过的周期计数将是相同的,即使前者在1%的时间内完成后续计算.