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
}
]
}

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

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

  1. Chrome development tools学习笔记(5)

    调试JavaScript 随着如今JavaScript应用的越来越广泛,在面对前端工作的时候,开发人员须要强大的调试工具来高速有效地解决这个问题.我们文章的主角,Chrome DevTools就提供了 ...

  2. Chrome development tools学习笔记(3)

    (上次DOM的部分做了些补充,欢迎查看Chrome development tools学习笔记(2)) 利用DevTools Elements工具来调试页面样式 CSS(Cascading Style ...

  3. 微软专家推荐11个Chrome 插件

    Web开发人员,需要长时间使用浏览器,尽管Windows10 Edge浏览器启动非常快速,且支持110多种设备,Edge支持基于JS 扩展,但也删除了很多旧功能像Active-X等插件.多数情况下,插 ...

  4. 11月份 chrome 标签整理

    Spring MVC框架相关 Java Web开发 和 linux下开发 汇总 项目源码 优秀的音视频开源框架 常用软件的下载 学习资源或网站 最后分享一些以前收藏的优秀博客 这两天经过3次面试,很幸 ...

  5. 11.在Chrome谷歌浏览器中安装插件XPath Helper的方法

    1.首先在以下链接下载XPath Helper插件,链接:https://pan.baidu.com/s/1Ng7HAGgsVfOyqy6dn094Jg 提取码:a1dv 2.插件下载完成后解压,然后 ...

  6. 【读书笔记】深入应用C++11代码优化与工业级应用 读书笔记01

    第一章 使用C++11让程序更简洁.更现代 1.1  类型推导 1.1.1  auto类型推导 1.auto关键字的新意义 不同于python等动态类型语言的运行时进行变量类型的推导,隐式类型定义的类 ...

  7. 11月15日jquery学习笔记

    1.属性 jQuery对象是类数组,拥有length属性和介于0~length-1之间的数值属性,可以用toArray()方法将jQuery对象转化为真实数组. selector属性是创建jQuery ...

  8. Chrome浏览器启动参数大全(命令行参数)

    前言 在开发Web项目当中,浏览器必不可少,而浏览器的启动参数可以帮我们实现很多功能. 常用参数 常用参数请参考下表. 序号 参数 说明 1 --allow-outdated-plugins 不停用过 ...

  9. 那些你不知道的chrome URLs

    Xee:我用的是七星浏览器,因为我看了很多的浏览器,它们的版本都停滞不前了: 360安全浏览器的重度用户肯定不会对 se:last (上次未关闭页面)这个页面感到陌生,即使您没有见过这个,但也一定很熟 ...

随机推荐

  1. 如何集成 Spring Boot 和 ActiveMQ?

    对于集成 Spring Boot 和 ActiveMQ,我们使用依赖关系. 它只需要很少的配置,并且不需要样板代码.

  2. 什么是基于Java的Spring注解配置? 给一些注解的例子?

    基于Java的配置,允许你在少量的 Java注解 的帮助下,进行你的大部分Spring配置而非通过XML文件. 以@Configuration 注解为例,它用来标记类可以当做一个bean的定义,被Sp ...

  3. 如何在 Windows 和 Linux 上查找哪个线程使用的 CPU 时 间最长?

    使用 jstack 找出消耗 CPU 最多的线程代码

  4. synchronized使用及原理解析

    修饰静态方法.实例方法.代码块 Synchronized修饰静态方法,对类对象进行加锁,是类锁. Synchronized修饰实例方法,对方法所属对象进行加锁,是对象锁. Synchronized修饰 ...

  5. 如何在 Microsoft word中插入代码

    一.工具 方法1.打开这个网页PlanetB; 方法2.或者谷歌搜索syntax highlight code in word documents,检索结果的第一个.如下图: PS. 方法1和2打开的 ...

  6. 单总线协议DS1820

    一. DS18B20简介 DS18B20数字温度传感器接线方便,封装后可应用于多种场合,如管道式,螺纹式,磁铁吸附式,不锈钢封装式.主要根据应用场合的不同而改变其外观.封装后的DS18B20可用于电缆 ...

  7. 百度开放云 BOS Uploader

    百度开放云 BOS Uploader bce-bos-uploader 是基于 bce-sdk-js 开发的一个 ui 组件,易用性更好.DEMO地址是:http://leeight.github.i ...

  8. 将word文件转为excel文件

    有些word文件里的数据是有顺序或者规律,想转成表格的形式,下面就以我要转的word为例. 我的word文件是这样的 1.word转txt(文本文件) 文件--->另存为--->路径--- ...

  9. js判断输入数字是否是整数,金额、数字

    function isIntNum(strNum){//js判断输入数字是否是整数 仅供学习思想 var strCheckNum = strNum+""; if(strCheckN ...

  10. Thread中,run方法和start方法的区别

    1. 通过调用Thread类中的start()方法可以启动一个线程,但是线程并不是立刻运行,而是处于就绪态,一旦获取cpu时间片,则会立即运行run()方法 2. start()方法实现了多线程运行, ...