汇编:计算执行时间的指令

时间:2011-10-25 06:14:42

标签: performance assembly execution instructions

如何计算指令的执行时间?它只是通过检查芯片制造商在一个动作可能需要完成的时钟周期方面所说的内容来完成的吗?还有什么我应该知道的吗?感觉就像我错过了什么......

2 个答案:

答案 0 :(得分:4)

据我所知,RDTSC指令非常准确。

我认为如果你正在寻找精确的循环计数,那么在短的可提升部分的情况下,你可能会遇到Mysticial提到的同时性问题......

但是,如果超超超精确不是一个障碍......也就是说,如果你能够活下来,知道在某些情况下你的结果是......我不知道...说9到80个周期...然后我很确定你仍然可以用RDTSC得到非常准确的结果...特别是当人们认为9到80除以32亿是一个非常小的数字时:)

数字9和80被任意选择(也许你不是3.2ghz cpu速度)因为我不知道错误数量是多少...但我很确定它在那个球场:)

这是我使用的计时器功能的RDTSC摘录:

//High-Rez Setup
__asm
{
    push        eax
    push        edx
    rdtsc
    mov         [AbsoluteLow],eax
    mov         [AbsoluteHigh],edx
    pop         edx
    pop         eax
}

实际上我会继续并发布整个事情......这段代码假设“double”类型是64位浮点数...这可能不是通用的编译器/架构假设:

double              AbsoluteTime;
double              AbsoluteResolution;
ulong               AbsoluteLow;
ulong               AbsoluteHigh;



void Get_AbsoluteTime (double *time)
{
    //Variables
    double  current, constant;
    double  lower, upper;
    ulong   timelow, timehigh;

    //Use the Intel RDTSC
    __asm
    {
        push    eax
        push    edx
        rdtsc
        sub     eax, [AbsoluteLow]
        sbb     edx, [AbsoluteHigh]
        mov     [timelow], eax
        mov     [timehigh], edx
        pop     edx
        pop     eax
    }

    //Convert two 32bit registers to a 64-bit floating point
    //Multiplying by 4294967296 is similar to left-shifting by 32 bits
    constant     = 4294967296.0;
    lower        = (double) timelow;
    upper        = (double) timehigh;
    upper       *= constant;
    current      = lower + upper;
    current     /= AbsoluteResolution;
    current     += AbsoluteTime;
    *time        = current;
}



void Set_AbsoluteTime (double time, double scale)
{
    //Variables
    double  invScale;

    //Setup
    AbsoluteTime = time;

    //High-Rez Setup
    __asm
    {
        push    eax
        push    edx
        rdtsc
        mov     [AbsoluteLow],eax
        mov     [AbsoluteHigh],edx
        pop     edx
        pop     eax
    }

    //Fetch MHZ
    if (1)
    {
        //Local Variables
        int      nv;
        ulong    mhz;
        char     keyname[2048];

        //Default assumption of 3.2ghz if registry functions fail
        mhz = 3200;

        //Registry Key
        sprintf (keyname, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0");
        nv = Reg_Get_ValueDW (keyname, "~MHz", (ulong *)&mhz);

        //Transform into cycles per second
        mhz *= 1000000;

        //Calculate Speed Stuff
        AbsoluteResolution = (double) mhz;
        invScale  = 1.0;
        invScale /= scale;
        AbsoluteResolution *= invScale;
    }
}

你想在使用Get函数之前在某个地方调用Set_AbsoluteTime ...没有第一次初始调用Set,获取将返回错误的结果......但是一旦进行了那次一次调用你就可以了......

这是一个例子:

void Function_to_Profile (void)
{
    //Variables
    double   t1, t2, TimeElapsed;

    //Profile operations
    Get_AbsoluteTime (&t1);
    ...do stuff here...
    Get_AbsoluteTime (&t2);

    //Calculate Elapsed Time
    TimeElapsed = (t2 - t1);

    //Feedback
    printf ("This function took %.11f seconds to run\n", TimeElapsed);
}

void main (void)
{
    Set_AbsoluteTime (0.000, 1.000);
    Function_to_Profile();
}

如果由于某种原因你想让时间测量以半速向后流动(对于游戏编程来说可能很方便),那么初始调用将是: Set_AbsoluteTime(0.000,-0.500);

Set的第一个参数是添加到所有结果的基准时间

我很确定这些功能比目前公开存在的最高rez Windows API定时器更准确...我认为在快速处理器上它们的误差小于1纳秒但我并不是100%肯定在那:):

它们对于我的目的来说足够准确,但请注意40个前导字节的标准初始化(由'当前','常数','低','上','时间','时间高'组成')大多数C编译器设置为0xCC或0xCD会占用一些周期......在每个Get_AbsoluteTime调用的底部执行数学运算......

因此,为了获得真正的原始精度,您最好将任何想要在RDTSC“内联”中分析的内容框架化......我会利用扩展的x64寄存器来存储以后减法操作的答案而不是乱用内存访问速度较慢......

比如像这样的东西...这主要是概念,因为技术上VC2010不允许你通过__asm关键字发出x64-Assembly :( ......但我认为它将为您提供旅行的概念之路:

typedef unsigned long long ulonglong;
ulonglong Cycles;

__asm
{
    push rax
    push rdx
    rdtsc
    mov r9, edx
    shl r9, 32
    and rax, 0xFFFFFFFF
    or  r9, rax
    pop rdx
    pop rax
}

...Perform stuff to profile here

__asm
{
    push rax
    push rdx
    rdtsc
    mov r10, edx
    shl r10, 32
    and rax, 0xFFFFFFFF
    or  r10, rax
    sub r10, r9
    mov qword ptr [Cycles], r10
    pop rdx
    pop rax
}

printf ("The code took %s cycles to execute\n", ULONGLONG_TO_STRING (Cycles));

使用该代码我认为经过的周期数的最终答案将是r10,64位寄存器......或Cycles,64位无符号整数......只有少数几个周期的错误引起位移和堆栈操作...只要被分析的代码不会破坏r9和r10呵呵......我忘记了最稳定的扩展x64寄存器...

同样“和rax,0xFFFFFFFF”可能是无关紧要的,因为我不记得RDTSC是否将RAX的高32位归零...所以我将AND操作包括在内以防万一:)

答案 1 :(得分:1)

这是一项非常重要的任务。最简单的方法是查看其他人找到的结果。

例如,Agner Fog作为当前x86 / x64处理器上此信息的绝佳参考:http://www.agner.org/optimize/instruction_tables.pdf

如果您确实想自己测量指令延迟和吞吐量,则需要非常深入地了解处理器的工作原理。然后你将不得不深入到汇编编码。编写微基准测量这些东西几乎就是一个领域,因为需要进行大量的逆向工程。

当然,最终 - 应用程序的性能取决于更多因素,而不仅仅是指令延迟/吞吐量......