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. JDBC中大数据量的分页解决方法?

    最好的办法是利用sql语句进行分页,这样每次查询出的结果集中就只包含某页的数据内容. sql语句分页,不同的数据库下的分页方案各不一样,下面是主流的三种数据库的分页sql: oracle: selec ...

  2. spring-注解驱动模式

    spring web装配原理: /** * WebApplicationInitializer Spring MVC 提供接口. * * Spring中的web自动配置,也是可以, */ /** * ...

  3. gradle构建scala

    1. 在目录下创建build.gradle文件,内容为: apply plugin: 'idea' apply plugin: 'scala' repositories { mavenLocal() ...

  4. Eureka server

    Eureka server使用的不是spring mvc的框架,而是使用Jersey. Eureka server ,启动的流程,追本溯源,是在 DiscoveryClient里面,使用这个构造方法 ...

  5. ACM - 最短路 - CodeForces 295B Greg and Graph

    CodeForces 295B Greg and Graph 题解 \(Floyd\) 算法是一种基于动态规划的算法,以此题为例介绍最短路算法中的 \(Floyd\) 算法. 我们考虑给定一个图,要找 ...

  6. Azure DevOps (八) 通过流水线编译Docker镜像

    上一篇文章我们完成了最简单的传统部署:上传应用到服务器上使用守护进程进行应用的部署. 本篇文章我们开始研究容器化和流水线的协作. 在开始操作之前,我们首先需要准备一下我们的dockerfile,这里我 ...

  7. PCB产业链、材料、工艺流程详解(1)

    PCB知识大全 1.什么是pcb,用来干什么? PCB( Printed Circuit Board),中文名称为印制电路板,又称印刷线路板,是重要的电子部件,是电子元器件的支撑体,是电子元器件电气连 ...

  8. 安装ESLint

    安装ESLint ESLint是静态代码检查工具,配合TypeScript使用可以帮助检查TypeScript的语法和代码风格. 添加ESLint到当前工程,yarn add -D eslint. 使 ...

  9. 订单突破10000+,仅花1小时,APPx独家深入剖析背后的秘密!

    拼多多:成立三年,获客三亿,月订单成交额达到恐怖的400亿,成功上市! 糕妈优选:营销活动推送1小时,订单超过10000+,商品成功刷屏朋友圈! 寻慢:一场活动净增7000+粉丝,付款转化率高达71% ...

  10. 伪元素的margin值挤压主体元素解决

    伪元素的margin值挤压主体元素解决 主体是两个p标签,需要再其左侧添加一个竖线,很常见的需求 目标 前提条件 1. 右侧的文字个数不固定 问题 1. 需要让before元素为`float:left ...