如何在c编程中使用rdtsc来估计for循环的开销

问题描述:

我想在0到7的范围内通过增加参数来计算一个函数的开销。如何估算硬件开销和软件开销。如何在c编程中使用rdtsc来估计for循环的开销

+0

'gettimeofday'在任何POSIX系统上都能很好地工作。 'rdtsc'指令只存在于x86架构上,并且在所有这些指令上都不起作用,所以我建议避免它。 –

+0

@BenVoigt'clock_gettime'现在正在取代'gettimeofday'。我不确定何时,但手册页说它将来会被删除。 – tijko

+1

@tijko:有'gettimeofday()'的平台没有'clock_gettime()' - Mac OS X(10.11.4)就是这样。目前,'gettimeofday()'比'clock_gettime()'更具可移植性。 –

首先要做的是disassembly,在Linux上你可以从objdump获得帮助。 objdump将帮助您查看代码是如何生成的。如果你对这些传递的参数没有做任何处理,那么这只是从堆栈中获取参数并将它们保存在寄存器中的区别。由于这些操作将花费很少的CPU周期,因此,很难提供时序信息,但在CPU周期方面更容易。

你的问题不是很好摆出。但是,执行rdtsc指令最可靠的方法是使用内联汇编来调用它,这是所有C编译器完全支持的。由C标准规定的任何定时功能将随实施而变化。英特尔有一个非常好的白皮书,介绍实现rdtsc东西here的最佳方式。主要问题是无序执行,这可能超出了您的问题范围。

我发现的最好的实现是在this repo,我已经适应了我自己的使用。这组基本的宏,假设你有一个兼容的处理器,将会给你〜每个调用开销的32个时钟周期(你需要为你自己的处理器做测试):

#include <cpuid.h> 
#include <stdint.h> 

/*** Low level interface ***/ 

/* there may be some unnecessary clobbering here*/ 
#define _setClockStart(HIs,LOs) {           \ 
asm volatile ("CPUID \n\t"             \ 
       "RDTSC \n\t"             \ 
       "mov %%edx, %0 \n\t"           \ 
       "mov %%eax, %1 \n\t":           \ 
       "=r" (HIs), "=r" (LOs)::          \ 
       "%rax", "%rbx", "%rcx", "%rdx");        \ 
} 

#define _setClockEnd(HIe,LOe) {            \ 
asm volatile ("RDTSCP \n\t"             \ 
       "mov %%edx, %0 \n\t"           \ 
       "mov %%eax, %1 \n \t"           \ 
       "CPUID \n \t": "=r" (HIe), "=r" (LOe)::      \ 
       "%rax", "%rbx", "%rcx", "%rdx");        \ 
} 
#define _setClockBit(HIs,LOs,s,HIe,LOe,e) {         \ 
    s=LOs | ((uint64_t)HIs << 32);           \ 
    e=LOe | ((uint64_t)HIe << 32);           \ 
} 

/*** High level interface ***/ 

typedef struct { 
    volatile uint32_t hiStart; 
    volatile uint32_t loStart; 
    volatile uint32_t hiEnd; 
    volatile uint32_t loEnd; 
    volatile uint64_t tStart; 
    volatile uint64_t tEnd; 

    /*tend-tstart*/ 
    uint64_t tDur; 
} timer_st; 

#define startTimer(ts)              \ 
{                   \ 
    _setClockStart(ts.hiStart,ts.loStart);         \ 
} 


#define endTimer(ts)              \ 
{                   \ 
    _setClockEnd(ts.hiEnd,ts.loEnd);           \ 
    _setClockBit(ts.hiStart,ts.loStart,ts.tStart,        \ 
     ts.hiEnd,ts.loEnd,ts.tEnd);           \ 
    ts.tDur=ts.tEnd-ts.tStart;            \ 
}                    

#define lapTimer(ts)              \ 
{                   \ 
    ts.hiStart=ts.hiEnd;              \ 
    ts.loStart=ts.loEnd;              \ 
} 

然后像这样的东西

#include <stdio.h> 
#include <math.h> 
#include "macros.h" /* Macros for calling rdtsc above */ 

#define SAMPLE_SIZE 100000 

int main() 
{ 
    timer_st ts; 
    register double mean=0; 
    register double variance=0; 
    int i; 

    /* "Warmup" */ 
    for(i=1;i<SAMPLE_SIZE;i++) 
    { 
    startTimer(ts); 
    endTimer(ts); 
    } 

    /* Data collection */ 
    for(i=1;i<SAMPLE_SIZE;i++) 
    { 
    startTimer(ts); 
    endTimer(ts); 
    mean+=ts.tDur; 
    } 

    mean/=SAMPLE_SIZE; 

    fprintf(stdout,"SampleSize: %d\nMeanOverhead: %f\n", SAMPLE_SIZE,mean); 


    return 0; 
} 

把它在我的Broadwell微架构的芯片,我得到这个输出

SampleSize: 100000 
MeanOverhead: 28.946490 

29时钟分辨率的时钟分辨率是相当不错的。人们通常使用的任何库函数(如gettimeofday)都不具备时钟级精度和200-300的开销。

我不确定你的意思是“硬件开销”与“软件开销”,但对于上面的实现,没有函数调用来执行rdtsc调用之间的时间或中间代码。所以我想软件开销是零。

+0

'rdtsc'本质上是芯片专用的,并且仅限于(某些)英特尔CPU。编译器通常支持'asm',但是使用的符号有所不同 - 然而,它不是由C标准规定的(例如,'asm'不是标准C-ISO/IEC 9899:2011中的关键字)。标准的C(或POSIX)定时功能在各种平台和CPU类型中更加可靠,并且性能可靠 - 尽管(比编译器直接访问“rdtsc”指令更慢)。 –