草庐IT

关于BenchMark/c++11计时器/Chrome:tracing 的一些笔记

Satar07 2023-03-28 原文

转载请标明出处: https://www.cnblogs.com/Multya/p/16213025.html

A benchmark is a test of the performance of a computer system.

​ 基准测试是对计算机系统的性能的测试

计时器

性能的指标就是时间,在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(), '"', '\'');

好啦 包装弄好了 下一步开始高效插桩

打桩

神说:“我怕麻烦。”

于是就有了宏

低级打桩

先看

void core1() {
    InstrumentationTimer tt("halo world 0 to 9999");
    for (int i = 0; i < 10000; ++i) {
        cout << "Hello world #" << i << endl;
    }
}

void benchMark() {
    InstrumentationTimer tt("shart benchMark");
    core1();
}

在一个函数的开头放上计时器 计时器就会自动记录这个作用域自它定义开始到结束所经过的时间和有关的信息

在计时器销毁前几微秒 它会将它所看到的的东西传给Instrumentor来记录所发生的事情

但是!!这未免也太傻了

为什么还要我手动给一个名字

让它自动生成独一无二的名字就行了嘛

中级打桩

有那么个宏 是所有编辑器都能自动展开的 叫 __FUNCTION__ 它会变成它所在的函数的名字的字符串

于是就有了

#define PROFILE_SCOPE(name) InstrumentationTimer tt(name)
#define PROFILE_FUNCTION() PROFILE_SCOPE(__FUNCTION__)
void core1() {
    PROFILE_FUNCTION();
    for (int i = 0; i < 10000; ++i) {
        cout << "Hello world #" << i << endl;
    }
}

void benchMark() {
    PROFILE_FUNCTION();
    core1();
}

好 但还不够好

所有的计时器都是一个名称 万一不小心重名了 那事情就不好整了

又有一个宏 叫 __LINE__ 它会变成所在行号(数字)

而宏能用神奇的 #将东西黏在一起

就有了

#define PROFILE_SCOPE(name) InstrumentationTimer tt##__LINE__(name)

好 但还不够好

万一我的函数是重载的 输出的是一样的函数名字 我咋知道调用的是哪个版本的函数

又有一个宏 叫 __PRETTY_FUNCTION__ MSVC是 __FUNCSIG__它能变成完整的函数签名的字符串 就像 "void core1(int)"

#define PROFILE_FUNCTION() PROFILE_SCOPE(__PRETTY_FUNCTION__)

好 但还不够好

这个我可不想把它保留在release下 让用户也帮我测测时间 怎么才能方便的关掉呢

对 还是宏

高级打桩

#define PROFILING 1
#if PROFILING
#define PROFILE_SCOPE(name) InstrumentationTimer tt##__LINE__(name)
#define PROFILE_FUNCTION() PROFILE_SCOPE(__PRETTY_FUNCTION__)
#else
#define PROFILE_SCOPE(name)
#define PROFILE_FUNCTION()
#endif

void core(int useless) {
    PROFILE_FUNCTION();
    for (int i = 0; i < 10000; ++i) {
        cout << "Hello world #" << i << endl;
    }
}

void core() {
    PROFILE_FUNCTION();
    for (int i = 0; i < 10000; ++i) {
        cout << "Hello world #" << sqrt(i) << endl;
    }
}

void benchMark() {
    PROFILE_FUNCTION();
    core(23333);
    core();
}

这就是了 如果我想关掉测试 就把profiling设为1 这是所有测试都只是空行 而release对于没有使用的函数则自动删去了 丝毫不影响性能

多线程

扩展

拓展ProfileResult

struct ProfileResult {
    string Name;
    long long start, end;
    uint32_t TheadID;
};

更改输出

m_OutputStream << R"("tid":)" << result.TheadID << ",";

在Timer中捕获该线程的id 并用自带hash转换成uint32方便输出

uint32_t threadID = hash<std::thread::id>{}(std::this_thread::get_id());

传递id

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

最后变成了这样

~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();

    uint32_t threadID = hash<std::thread::id>{}(std::this_thread::get_id());

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

测试

搞一个多线程出来

void benchMark() {
    PROFILE_FUNCTION();
    cout << "Running BenchMarks...\n";
    thread a([]() { core(23333); });
    thread b([]() { core(); });

    a.join();
    b.join();
}

用lamda可以非常简洁的开多线程重载函数

最后加入2个join函数 这样在这两个线程都完成它们的工作之前 我们不会真正退出这个benchmark函数

完成

好啦 我们的工作完成了 欣赏一下代码吧

#include <bits/stdc++.h>
#include <sstream>

using namespace std;

struct ProfileResult {
    string Name;
    long long start, end;
    uint32_t TheadID;
};

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":)" << result.TheadID << ",";
        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;
    }
};


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();

        uint32_t threadID = hash<std::thread::id>{}(std::this_thread::get_id());

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

#define PROFILING 1
#if PROFILING
#define PROFILE_SCOPE(name) InstrumentationTimer tt##__LINE__(name)
#define PROFILE_FUNCTION() PROFILE_SCOPE(__PRETTY_FUNCTION__)
#else
#define PROFILE_SCOPE(name)
#define PROFILE_FUNCTION()
#endif

void core(int useless) {
    PROFILE_FUNCTION();
    for (int i = 0; i < 10000; ++i) {
        cout << "Hello world #" << i << endl;
    }
}

void core() {
    PROFILE_FUNCTION();
    for (int i = 0; i < 10000; ++i) {
        cout << "Hello world #" << sqrt(i) << endl;
    }
}

void benchMark() {
    PROFILE_FUNCTION();
    cout << "Running BenchMarks...\n";
    thread a([]() { core(23333); });
    thread b([]() { core(); });

    a.join();
    b.join();
}


int main() {
    Instrumentor::Get().BeginSession();
    benchMark();
    Instrumentor::Get().EndSession();
}

最后的json

{
  "otherData": {},
  "traceEvents": [
    {
      "cat": "function",
      "dur": 3844575,
      "name": "void core(int)",
      "ph": "X",
      "pid": 0,
      "tid": 1709724944,
      "ts": 24887197644
    },
    {
      "cat": "function",
      "dur": 4039317,
      "name": "void core()",
      "ph": "X",
      "pid": 0,
      "tid": 2740856708,
      "ts": 24887197714
    },
    {
      "cat": "function",
      "dur": 4040539,
      "name": "void benchMark()",
      "ph": "X",
      "pid": 0,
      "tid": 2850328247,
      "ts": 24887196811
    }
  ]
}

细心的小伙伴可以推一推运行这段代码时间是什么时候呢~

本文链接: https://www.cnblogs.com/Multya/p/16213025.html

有关关于BenchMark/c++11计时器/Chrome:tracing 的一些笔记的更多相关文章

  1. ruby-on-rails - 如何生成传递一些自定义参数的 `link_to` URL? - 2

    我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些

  2. ruby - 安装libv8(3.11.8.13)出错,Bundler无法继续 - 2

    运行bundleinstall后出现此错误:Gem::Package::FormatError:nometadatafoundin/Users/jeanosorio/.rvm/gems/ruby-1.9.3-p286/cache/libv8-3.11.8.13-x86_64-darwin-12.gemAnerroroccurredwhileinstallinglibv8(3.11.8.13),andBundlercannotcontinue.Makesurethat`geminstalllibv8-v'3.11.8.13'`succeedsbeforebundling.我试试gemin

  3. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  4. ruby - 找一些句子 - 2

    我想找到在某些文本中找到一些(让它是两个)句子的好方法。什么会更好-使用正则表达式或拆分方法?你的想法?应JeremyStein的要求-有一些例子示例:输入:ThefirstthingtodoistocreatetheCommentmodel.We’llcreatethisinthenormalway,butwithonesmalldifference.IfwewerejustcreatingcommentsforanArticlewe’dhaveanintegerfieldcalledarticle_idinthemodeltostoretheforeignkey,butinthis

  5. ruby - 下载位置 Selenium-webdriver Cucumber Chrome - 2

    我将Cucumber与Ruby结合使用。通过Selenium-Webdriver在Chrome中运行测试时,我想将下载位置更改为测试文件夹而不是用户下载文件夹。我当前的chrome驱动程序是这样设置的:Capybara.default_driver=:seleniumCapybara.register_driver:seleniumdo|app|Capybara::Selenium::Driver.new(app,:browser=>:chrome,desired_capabilities:{'chromeOptions'=>{'args'=>%w{window-size=1920,1

  6. ruby-on-rails - 关于 Ruby 的一般问题 - 2

    我在我的rails应用程序中安装了来自github.com的acts_as_versioned插件,但有一段代码我不完全理解,我希望有人能帮我解决这个问题class_eval我知道block内的方法(或任何它是什么)被定义为类内的实例方法,但我在插件的任何地方都找不到定义为常量的CLASS_METHODS,而且我也不确定是什么here,并且有问题的代码从lib/acts_as_versioned.rb的第199行开始。如果有人愿意告诉我这里的内幕,我将不胜感激。谢谢-C 最佳答案 这是一个异端。http://en.wikipedia

  7. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  8. ruby - ri 有空文件 – Ubuntu 11.10, Ruby 1.9 - 2

    我正在运行Ubuntu11.10并像这样安装Ruby1.9:$sudoapt-getinstallruby1.9rubygems一切都运行良好,但ri似乎有空文档。ri告诉我文档是空的,我必须安装它们。我执行此操作是因为我读到它会有所帮助:$rdoc--all--ri现在,当我尝试打开任何文档时:$riArrayNothingknownaboutArray我搜索的其他所有内容都是一样的。 最佳答案 这个呢?apt-getinstallri1.8编辑或者试试这个:(非rvm)geminstallrdocrdoc-datardoc-da

  9. ruby block 并从 block 中返回一些东西 - 2

    我正在使用ruby​​1.8.7。p=lambda{return10;}deflab(block)puts'before'putsblock.callputs'after'endlabp以上代码输出为before10after我将相同的代码重构到这里deflab(&block)puts'before'putsblock.callputs'after'endlab{return10;}现在我收到LocalJumpError:意外返回。对我来说,这两个代码都在做同样的事情。是的,在第一种情况下我传递了一个过程,在第二种情况下我传递了一个block。但是&block将该block转换为pro

  10. ruby - 我怎样才能更好地了解/了解更多关于 Ruby 的知识? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我最近开始学习Ruby,这是我的第一门编程语言。我对语法感到满意,并且我已经完成了许多只教授相同基础知识的教程。我已经写了一些小程序(包括我自己的数组排序方法,在有人告诉我谷歌“冒泡排序”之前我认为它非常聪明),但我觉得我需要尝试更大更难的东西来理解更多关于Ruby.关于如何执行此操作的任何想法?

随机推荐