【问题标题】:Assembly: Compute Execution Time of Instructions汇编:计算指令的执行时间
【发布时间】:2011-10-25 06:14:42
【问题描述】:

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

【问题讨论】:

    标签: performance assembly execution instructions


    【解决方案1】:

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

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

    但如果超超超超精度不是障碍...也就是说,如果您知道在某些情况下您的结果会偏离...我不知道...比如说 9到 80 个周期...那么我很确定您仍然可以使用 RDTSC 获得非常准确的结果...尤其是当人们认为 9 到 80 除以 32 亿是一个非常小的数字时:)

    数字 9 和 80 的选择有点随意(也许你的 cpu 速度也不是 3.2ghz),因为我不知道错误量到底是多少......但我很确定它在那个范围内:)

    这是我使用的计时器函数的 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,Get 将返回错误结果...但是一旦进行了一次调用,您就可以开始了...

    这是一个例子:

    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 的第一个参数是添加到所有结果中的基准时间

    我很确定这些函数比目前公开存在的最高分辨率的 Windows API 计时器更准确...我认为在快速处理器上它们的误差小于 1 纳秒,但我不是 100% 确定在那:)

    它们对于我的目的来说足够准确,但请注意 40 个前导字节的标准初始化(由 'current'、'constant'、'lower'、'upper'、'timelow'、'timehigh ') 大多数 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 寄存器是什么...

    “and rax, 0xFFFFFFFF”也可能是无关紧要的,因为我不记得 RDTSC 是否将 RAX 的高 32 位清零...所以我包含了 AND 操作以防万一:)

    【讨论】:

    • 哇,谢谢,我会仔细阅读您的回答。非常感谢您花时间回答这个问题
    • 想知道不接受... +1,这就解释了!关于如何使用rdtsc 的非常好的答案。虽然我想补充一点,rdtsc 并不总是以 CPU 周期来衡量。虽然rdtsc 必须保持不变,但由于省电功能和涡轮增压,CPU 频率可以动态变化。 (例如,在我的机器上,rdtsc 提供 34 亿周期/秒,而处理器实际主频为 4.6 GHz)因此,当您使用 rdtsc 计算周期时,您需要进行适当的调整。跨度>
    • ahh dang...我可能是一只正在被淘汰的恐龙:p...我不知道 TSC 的动态性质...这篇维基百科文章也提到了一些问题...休眠、不同步的内核、不同的滴答率和省电都会产生影响,听起来像:en.wikipedia.org/wiki/Time_Stamp_Counter ...在我 2007 年购买的四核上我还没有遇到这些事情,但我我敢打赌,在我升级到 i7 或其他东西后遇到这些问题只是时间问题:p ...所以也许 QPC 方法毕竟更安全
    【解决方案2】:

    这是一项不平凡的任务。最简单的方法是查看其他人发现的结果。

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

    如果您真的想自己测量指令延迟和吞吐量,则需要非常深入地了解处理器的工作原理。然后你必须深入研究汇编编码。编写微基准来衡量这些事情本身几乎就是一个领域,因为需要进行大量的逆向工程。

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

    【讨论】:

    • 感谢您的回复。我更想知道我自己将如何做,查看一条指令所需的时钟周期数(显示在制造商的芯片表上)。我想我想知道的是,在我试图计算组合指令列表的执行时间的情况下,通过添加时钟周期总数来计算是否有意义......这样做会不会感觉?
    • 这实际上行不通,因为当今的处理器是超标量且无序的。这意味着它们能够同时执行多条指令,并且可以改变指令的顺序。所以不,运行时间不仅仅是所有指令的总和。因此,为什么这是一件非常难以衡量的事情。
    • 尽管如此,还是有一些标准的方法可以做到这一点。要测量延迟,您需要创建一个较长的依赖链。在所有操作数都准备好之前,一条指令无法执行,因此制作依赖链可以让您测量延迟,因为它可以防止乱序执行。吞吐量可以通过同时发出尽可能多的独立指令来衡量。
    • 谢谢,这不是我想要的,但我想我已经设法在你的指导下弄清楚了。我的问题可能会更清楚。这是我正在寻找的一个示例: MOVE.L (AO)+D0 这将需要 12 个时钟周期才能完成该指令。这是手册(第 122 页):freescale.com/files/32bit/doc/ref_manual/EC000UM.pdf 再次感谢。干杯!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-14
    • 2022-01-23
    相关资源
    最近更新 更多