关于BenchMark/c++11计时器/Chrome:tracing 问题

Posted Mr song song

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于BenchMark/c++11计时器/Chrome:tracing 问题相关的知识,希望对你有一定的参考价值。

计时器

性能的指标就是时间,在c++11后计时十分方便,因为有<chrono>神器

在性能测试中,一般依赖堆栈上的生命周期来进行计时

计时器的实现全貌

class InstrumentationTimer 
private:
    chrono::time_point<chrono::steady_clock> start;
    const char *m_hint;

public:
    explicit InstrumentationTimer(const char *hint) : m_hint(hint) 
        start = chrono::steady_clock::now();
    


    ~InstrumentationTimer() 
        auto end = chrono::steady_clock::now();
        cout << m_hint << ':' << static_cast<double>((end - start).count()) / 1e6 << "ms\\n";
        long long llst = chrono::time_point_cast<chrono::microseconds>(start).time_since_epoch().count();
        long long lled = chrono::time_point_cast<chrono::microseconds>(end).time_since_epoch().count();

        //Instrumentor::Get().WriteProfile(m_hint, llst, lled);
    
;

非常简单的原理 就是应用作用域自动调用析构函数来停止计时

唯一难搞的就是chrono的层层包装

本文非常功利 不深究底层 ~

time_pointer

chrono::time_point<chrono::steady_clock> start;

在chrono命名空间下(std下层) 有个神奇的类型 叫时间点time_point

在不同的操作环境下 有不同的实现 所以这是一个模板

模板类型可以有

  • chrono::high_resolution_clock 高解析度类型 不建议使用 因为这个可能有移植的问题 但好像进度最高?
  • chrono::steady_clock 稳得一批的钟 我超爱这个 因为这个不仅进度不低 而且调用的时间短,影响极小 (300ns
  • chrono::system_clock 系统带的钟 不大行 精度因系统而定? windows是100ns

所以 你懂的 用steady就好了(也不用太纠结几纳秒

给时间点一个当前时间 注意类型一致

start = chrono::steady_clock::now();

duration

auto  dur = end - start;

为啥用auto 因为方便昂(duration 模板具体化写到头皮发麻

时间点运算得到的是时间段 因为默认的时间点单位时间是纳秒(steady_clock),所以得到的时间是内部以longlong存储的多少纳秒

如何调出时间?

(end - start).count()

得到的是longlong ns

如何更改单位时间?

一个是转换时间段的格式

chrono::duration_cast<chrono::microseconds>(end - start).count())

一个是转换时间点的格式

chrono::time_point_cast<chrono::microseconds>(start)

如何调出一个时间戳?(系统从我也不知道哪开始算起的时间段 1970.1.1大概? 相当于帮你减了一下

start.time_since_epoch().count()

可选格式:

  • chrono::nanoseconds

  • chrono::microseconds

  • chrono::milliseconds

  • chrono::seconds

  • chrono::minutes

  • chrono::hours

回到实现

构造函数没啥好讲的 就是开始计时

重点是析构函数

~InstrumentationTimer() 
        auto end = chrono::steady_clock::now();
        cout << m_hint << ':' << static_cast<double>((end - start).count()) / 1e6 << "ms\\n";
        long long llst = chrono::time_point_cast<chrono::microseconds>(start).time_since_epoch().count();
        long long lled = chrono::time_point_cast<chrono::microseconds>(end).time_since_epoch().count();

        Instrumentor::Get().WriteProfile(m_hint, llst, lled);
    

思路:

  • 首先!!!一定先停止计时 (你不会还想增大误差吧) 用auto接住 省一个成员

  • 然后 输出的是你要计时的位置的注释(hint) 接一个时间段

    因为时间段输出的是longlong 我看多了几点几ms觉得非常亲切 所以用纳秒算时间段(默认)后再除1e6得到毫秒

  • 留两个时间戳后面有用

  • 然后是后面的调用记录某一段程序运行时间的函数啦 这里传进去的有hint 开始和结束的时间戳 有了这些 你就能算出经过的时间

整理输出部分

Chrome大法好

chromo 自带了个可视化分析软件 在地址栏上输入chrome://tracing/就可以看到

它接受的是json文件 所以我们要把我们记录下来的东西打包成json拖到界面上 就可以看到精美(并不) 的可视化界面

这是打包器+记录器的全貌

class Instrumentor 
private:
    ofstream m_OutputStream;
    bool m_Fir;

public:
    Instrumentor() : m_Fir(true) 

    void BeginSession(const string &filepath = "results.json") 
        m_OutputStream.open(filepath);
        WriteHeader();

    

    void EndSession() 
        WriteFooter();
        m_OutputStream.close();
        m_Fir = true;
    

    void WriteProfile(const ProfileResult &result) 
        if (!m_Fir)  //not add ',' when first time
            m_OutputStream << ',';
         else m_Fir = false;

        string name(result.Name);
        replace(name.begin(), name.end(), '"', '\\'');
        m_OutputStream << R"()";
        m_OutputStream << R"("cat":"function",)";
        m_OutputStream << R"("dur":)" << result.end - result.start << ",";
        m_OutputStream << R"("name":")" << name << "\\",";
        m_OutputStream << R"("ph":"X",)";
        m_OutputStream << R"("pid":0,)";
        m_OutputStream << R"("tid":0,)";
        m_OutputStream << R"("ts":)" << result.start;
        m_OutputStream << R"()";
        m_OutputStream.flush();
    

    void WriteHeader() 
        m_OutputStream << R"("otherData":,"traceEvents":[)";
        m_OutputStream.flush();
    

    void WriteFooter() 
        m_OutputStream << "]";
        m_OutputStream.flush();
    

    static Instrumentor &Get() 
        static auto instance = new Instrumentor();
        return *instance;
    
;

以及我们的目标 Chrome能识别的json文件


  "otherData": ,
  "traceEvents": [
    
      "cat": "function",
      "dur": 2166411,
      "name": "void core1(int)",
      "ph": "X",
      "pid": 0,
      "tid": 0,
      "ts": 19699253339
    ,
    
      "cat": "function",
      "dur": 1649285,
      "name": "void core2()",
      "ph": "X",
      "pid": 0,
      "tid": 0,
      "ts": 19701420118
    ,
    
      "cat": "function",
      "dur": 3816266,
      "name": "void benchMark()",
      "ph": "X",
      "pid": 0,
      "tid": 0,
      "ts": 19699253338
    
  ]

Get( )

首先看到最后的Get( )

static Instrumentor &Get() 
    static auto instance = new Instrumentor();
    return *instance;

这个能提供给我们一个单例,就是仅存在一个与我们运行时的对象

static 显式的指出Get得到的东西是和我们exe文件存在时间一样长的 而且这个定义只执行一次

如果你没有听懂 就只要记住它返回的永远是同一个对象 要用这个对象的时候就用Get

该这么用:

Instrumentor::Get().balabala();

初始化

private:
    ofstream m_OutputStream;
    bool m_Fir;

public:
    Instrumentor() : m_Fir(true) 

    void BeginSession(const string &filepath = "results.json") 
        m_OutputStream.open(filepath);
        WriteHeader();

    

    void EndSession() 
        WriteFooter();
        m_OutputStream.close();
        m_Fir = true;
    


ofsteam文件输出流用于输出到文件默认是results.json

不要忘记列表中的逗号的处理 我们用m_Fir检测是不是第一个

然后是注意到json开头和结尾是固定的

void WriteHeader() 
    m_OutputStream << R"("otherData":,"traceEvents":[)";
    m_OutputStream.flush();


void WriteFooter() 
    m_OutputStream << "]";
    m_OutputStream.flush();

R"( string )"即原始字符串 可以输出字符串里面的原本的字符 感兴趣的可以自行拓展更多有关知识 这里用了之后就不用打转义的双引号了

每次输出到文件时记得及时刷新 m_OutputStream.flush();防止之后的线程出现毛病

ok 现在我们可以这么用了

int main() 
    Instrumentor::Get().BeginSession();
    benchMark(); //测试的函数放这里
    Instrumentor::Get().EndSession();

中间列表的填写

但是?最最最重要的中间列表的填写呢?

在这里

void WriteProfile(const ProfileResult &result) 
    if (!m_Fir)  //not add ',' when first time
        m_OutputStream << ',';
     else m_Fir = false;

    string name(result.Name);
    replace(name.begin(), name.end(), '"', '\\'');
    m_OutputStream << R"()";
    m_OutputStream << R"("cat":"function",)";
    m_OutputStream << R"("dur":)" << result.end - result.start << ",";
    m_OutputStream << R"("name":")" << name << "\\",";
    m_OutputStream << R"("ph":"X",)";
    m_OutputStream << R"("pid":0,)";
    m_OutputStream << R"("tid":0,)";
    m_OutputStream << R"("ts":)" << result.start;
    m_OutputStream << R"()";
    m_OutputStream.flush();

在InstrumentationTimer中的调用:

//m_hint 是计时器注释  llst 开始时间戳  lled 结束时间戳
Instrumentor::Get().WriteProfile(m_hint, llst, lled);

定义传进来的参数 可以扩展

struct ProfileResult 
    string Name;
    long long start, end;
;

就是简单的往里面塞东西啦

值得注意的是 chrome 的tracing 默认时间戳的单位时间是microseconds 即毫秒 所以要记得转换格式哦

long long llst = chrono::time_point_cast<chrono::microseconds>(start).time_since_epoch().count();
long long lled = chrono::time_point_cast<chrono::microseconds>(end).time_since_epoch().count();

考虑到传进来的函数名字可能会带有" " 让json出错 所以退而求其次 把它转成 ' ' (其实在前面加一个转义字符更好 但是实现起来太麻烦了)

string name(result.Name);
replace(name.begin(), name.end(), '"', '\\'');

以上是关于关于BenchMark/c++11计时器/Chrome:tracing 问题的主要内容,如果未能解决你的问题,请参考以下文章

Winforms 傻瓜计时器

STM32定时器US延时

linux定时器

C#中简单的问题,关于WPF程序的计时器控件。

关于计时器的js函数

关于定时器ETR计数功能