zoukankan      html  css  js  c++  java
  • 程序运算性能测量

    定量分析是一切优化过程中最重要的过程之一。在性能优化和分析中,我们如何来描述一段代码的执行效率呢?这里,我将这一过程分为2步。

    第一步:我们要找到一个客观的、相对稳定的单位,来描述一个程序的运行时间;

    第二步:构造合理的、更接近起初情况的测试用例来进行测量。

    这样,最终得到的结果,就是对于一段代码执行效率的客观描述。

    一、单位确定

    首先,我们要确定下来描述单位。通常来说,时间差被广泛地用做描述计算能力的单位。顾名思议,就是在代码前记下一个时间T1,在执行后记下一个时间T2。然后通过计算 = T2 - T1即可得到执行的时长。这种方法方便、快捷,而且也较为准确。它被广泛地用于对计算能力的约束上。如,暴力破解某某密码要几小时等等。相信大家都熟悉下面一段话:

    “19769月,美国数学家阿佩尔和哈肯利用IBM360计算机工作了1200多个小时,终于证明了四色问题是正确的,轰动了世界。从此四色问题变成了四色定理。

    细心的读者一定已经发现,单纯的“1200多个小时是不能够描述四色问题的验证过程时间的,它往往要跟上一个前提,在IBM360机器,要求有同型号、子型号、同频率、同批次的CPU,才可以得到相同的结果。那么,是否有更加客观的单位可以描述代码的执行能力呢?这个单位一定符合5个条件。

    1)精度高——它不可能比时间精度低,通过gettimeofday得到的时间可以精确至clip_image001

    2)时间相关——我们可以从它经过计算转换为相对时间:秒;

    3)低耦合——尽量不要与CPU的主频等主要参数有过多的关系,换一台机器后它的值变化不会很大;

    4)相对稳定——不会导致前后2次测量的变化过大。

    CPU时钟周期,我们想到了这个基础单位。它的单位是Tick,对于主流的0.5GHz-2.0GHz的机器上,它分别可以精确至clip_image002。我们可以将它与CPU主频进行相乘来得到相对时间。对于支持PipelineL1/L2 Cache,指令集相同的CPU,它的差别并不大。由于CPU周期是由硬件晶振控制的,在硬件不出故障情况下,它相对稳定。它完全符合精度高、时间相关、低耦合以及相对稳定4个特点。本文后续的讨论将全部基于CPU时间周期来进行讨论。

    二、测量方法

    我们已经确定了用纳秒级的计量单位“CPU时钟周期。如何才能获得系统当前的CPU时钟周期呢?INTEL® CPU提供了一个64位的寄存器TSC来存储自开机来的周期值。并可通过指令“rdtsc”将其写入edx:eax。所以,我们可以在代码中插入这样的语句。

     

     

    #define rdtscll_64(val) do {
    unsigned int __a,__d; 
    __asm__ __volatile__("rdtsc" : "=a" (__a), "=d" (__d)); 
    (val) = (((unsigned long long)__d)<<32) | (__a); 
    } while(0);

     

    通过调用rdtscll即可以得到64位的CPU时钟周期数。

    三、测量误差

    在了解测量方法之后,同样我们要了解到这个测量方法在哪些情况下会产生误差。

    TSC本身来说,最常见的误差便是多核的影响。CPU并不会对多个核的tsc进行同步,所以,我们首先要保证测量过程是在同一个核上进程的。如果被测代码段执行过程中存在进程切换,则测量值一定不会很准。所以如果我们在测量的过程中要保证被测量函数不要进行进程切换。也就是说如果被测函数有异步IO调用,我们则需要将进程绑定在一个核上。

    由于cache的存在,导致我们在对访问密集型的代码进行测量的时候,前后2次测量的结果可能相差5-10倍,所以当我们希望获得单次非cache内内存获得开销的时候,我们需要需要取构造一个复杂的case

    时下非常流行的虚拟机技术也会影响测量的结果,因为有些虚拟机自己模拟了这个TSC这样我们拿到的值就是物理机硬件的tsc。因为在对虚拟机进行统计的时候需要关注它是如何获得TSC的。

    当然,TSC本身也是有开销的,虽然它很小。大概在20-40个周期。

    四、基本操作测量

    1. 连续内存写操作

    非常简单,只要memset一个大内存就OK

    2. 随机内存写操作

    我们需要构造一个大的block_list,每2个相信节点间的距离都要大于 cache大小,且并不相等,前、后关系也不尽相同。同时,我们通过 memset一个和cache相同大小的buff来清空cache。便可以测量出随机内存写操作的开销。

    3. 一些基本操作的开销

    操作

    平均周期数

    操作

    平均周期数

    连续内存访问(4byte)

    10

    浮点数值运算(+-*)

    10-12

    随机内存访问(4byte)

    208

    浮点数值运算(/

    14

    整数数值运算

    7

    系统随机

    46

       

    三角函数(sin/cos

    135

    五、范例:函数耗时分析

    测试使用的CPU信息如下:

    clip_image004

    范例分别用CTscStatCTimeStat类来测量funcAfuncBfuncC,用usleep来模拟真实函数的耗时。

    代码说明:

    #include <unistd.h>  
    #include <stdlib.h>  
    #include <stdio.h>  
    #include <string.h>  
    #include <errno.h>  
    #include <fcntl.h>
    #include <sys/time.h>
    #include <string>
    #include <vector>
    #include <stdint.h>
    
    #define rdtscll_64(val) do {
    unsigned int __a,__d; 
    __asm__ __volatile__("rdtsc" : "=a" (__a), "=d" (__d)); 
    (val) = (((unsigned long long)__d)<<32) | (__a); 
    } while(0);
    
    class CTscStat
    {
    public:
        CTscStat(const std::string& sCallerName)
        : m_sCallerName(sCallerName)
        {
            rdtscll_64(m_start);
            rdtscll_64(m_lastcall);
        }
    
        ~CTscStat()
        {
            uint64_t end;
            rdtscll_64(end);
            uint64_t totalConsumeTsc = end - m_start;
            printf("TscStat---TOTAL:func=[%s] tsc_cost=[%lu]
    ", m_sCallerName.c_str(), totalConsumeTsc);
        }
    
        void AddCall(const std::string& sCall)
        {
            AddCall(sCall, GetTscSub());
        }
    
    private:
        void AddCall(const std::string& sCallerName, uint64_t consumeTsc)
        {
            printf("TscStat---SubCall:func=[%s] sub_func=[%s] tsc_cost=[%lu]
    ",
                m_sCallerName.c_str(), sCallerName.c_str(), consumeTsc);
            rdtscll_64(m_lastcall);
        }
    
        uint64_t GetTscSub()
        {
            uint64_t now;
            rdtscll_64(now);
            
            return now - m_lastcall;
        }
    
    private:
        struct SCall
        {
            std::string sCallerName;
            uint64_t    consumeTsc;
        };
    
    private:
        std::string         m_sCallerName;  // 统计接口
        std::vector<SCall>  m_vecCall;      // 请求列表
    
        uint64_t            m_start;        // 开始TSC
        uint64_t            m_lastcall;     // 上次TSC
    };
    
    #define TSC_START(sCn) CTscStat oTscS(sCn);
    #define TSC_APICALL(API) 
            do { 
                oTscS.AddCall(API); 
            } while(0)
    
    class CTimeStat
    {
    public:
        CTimeStat(const std::string& sCallerName)
        : m_sCallerName(sCallerName)
        {
            gettimeofday(&m_start, 0);
            gettimeofday(&m_lastcall, 0);
        }
    
        ~CTimeStat()
        {
            struct timeval end;
            struct timeval subresult;
    
            gettimeofday(&end, 0);
            timersub(&end, &m_start, &subresult);
            uint32_t dwTotalConsumeTime = subresult.tv_sec * 1000 + subresult.tv_usec / 1000;
    
            printf("TimeStat---TOTAL:func=[%s] time_cost=[%u]
    ", m_sCallerName.c_str(), dwTotalConsumeTime);
        }
    
        void AddCall(const std::string& sCall)
        {
            AddCall(sCall, GetTimeSub());
        }
    
    private:
        void AddCall(const std::string& sCallerName, uint32_t dwConsumeTime)
        {
            printf("TimeStat---SubCall:func=[%s] sub_func=[%s] time_cost=[%u]
    ",
                m_sCallerName.c_str(), sCallerName.c_str(), dwConsumeTime);
            gettimeofday(&m_lastcall, 0);
        }
    
        uint32_t GetTimeSub()
        {
            struct timeval now;
            struct timeval subresult;
            gettimeofday(&now, 0);
    
            timersub(&now, &m_lastcall, &subresult);
    
            return subresult.tv_sec * 1000 + subresult.tv_usec / 1000;
        }
    
    private:
        struct SCall
        {
            std::string sCallerName;
            uint32_t    dwConsumeTime;
        };
    
    private:
        std::string         m_sCallerName;  // 统计接口
        std::vector<SCall>  m_vecCall;      // 请求列表
    
        struct timeval      m_start;        // 开始时间
        struct timeval      m_lastcall;     // 上次请求时间
    };
    
    #define TS_START(sCn) CTimeStat oTimeS(sCn);
    #define TS_APICALL(API) 
            do { 
                oTimeS.AddCall(API); 
            } while(0)
    
    void funcA()
    {
        usleep(1000); // 1ms
    }
    
    void funcB()
    {
        usleep(100000); // 100ms
    }
    
    void funcC()
    {
        sleep(1); // 1s
    }
    
    void TestTsc()
    {
        TSC_START("TestTsc");
        funcA();
        TSC_APICALL("funcA");
        funcB();
        TSC_APICALL("funcB");
        funcC();
        TSC_APICALL("funcC");
    }
    
    void TestTime()
    {
        TS_START("TestTsc");
        funcA();
        TS_APICALL("funcA");
        funcB();
        TS_APICALL("funcB");
        funcC();
        TS_APICALL("funcC");
    }
    
    int main(int argc, char** argv)  
    { 
        ////////// tsc ///////////
        TestTsc();
        
        ///////// time ///////////
        TestTime();
        
        return 0;
    }

    结果说明:

    [root@rocket linux_programming]# ./module_perform

    TscStat---SubCall:func=[TestTsc] sub_func=[funcA] tsc_cost=[6391184]

    TscStat---SubCall:func=[TestTsc] sub_func=[funcB] tsc_cost=[362433912]

    TscStat---SubCall:func=[TestTsc] sub_func=[funcC] tsc_cost=[3595441050]

    TscStat---TOTAL:func=[TestTsc] tsc_cost=[3964662011]

    TimeStat---SubCall:func=[TestTsc] sub_func=[funcA] time_cost=[2]

    TimeStat---SubCall:func=[TestTsc] sub_func=[funcB] time_cost=[100]

    TimeStat---SubCall:func=[TestTsc] sub_func=[funcC] time_cost=[1000]

    TimeStat---TOTAL:func=[TestTsc] time_cost=[1104]

    可以看到,当sleep 1ms的时候,测试结果与预期有较大差距,这个跟系统的精度有关。

    sleep 1s 100ms的时候用Tsc测量的结果分别是主频和主频的1/10

     

  • 相关阅读:
    【摘录】使用实体框架、Dapper和Chain的仓储模式实现策略
    关于微软HttpClient使用,避免踩坑
    在网上摘录一段对于IOC的解析,比较直观,大家观摩观摩
    经历招聘中面试求职的点滴
    在面试中忽然发现DateTime的一些...
    GCC的内存边界对齐
    如何删除Weblogic域
    电蚊拍选购参考
    Localizing WPF with .resx files
    C#操作串口总结
  • 原文地址:https://www.cnblogs.com/linuxbug/p/4954018.html
Copyright © 2011-2022 走看看