资讯专栏INFORMATION COLUMN

Celero:一个 C++ 的基准测试管理库

siberiawolf / 817人阅读

摘要:建立一个基线测试作比较很重要。然而,相对于一个经典的排序算法基线如快速排序,你可以说,处理万元素比去快。这一关联由测试向量来维护。接着,测试的开始时间被获取到并以毫秒的形式用一个保存起来。运行一个手动测试。对每一个基线组固定测试。

介绍

对代码进行持续性开发和有意义的基准测试是一个复杂的任务。虽然测试工具本身(Intel® VTune™ Amplifier, SmartBear AQTime, Valgrind)与应用程序没有相关性,但是它们在某些时候对一些小团队,或者说是一些繁琐的工作来说还是很重要的。这个Celero项目,主要是要建仓一个小型的程序库,使它可以在加入 C++ 工程和对代码进行基准测试时能够非常容易地去重建,分享,并允许在独立的运行进程、开发者或者是工程间进行比较。Celero 使用一个与 GoogleTest 相似的构架,使得他的 API 很容易地使用,并融入一个工程中。当你在开发过程中进行自动测试时,自动化基准将会扮演举足轻重的作用。

背景

通常,编写基准的目的是为了测量一段代码的性能。基准有助于比较解决同一问题的不同方案并选择其中最合适的一个。其他时候,基准能以一种很有意义的方式突出设计或算法改变后的性能影响。
通过测量代码的性能,可以为性能消除那些你以为是正确方案的错误。只有通过测量,你才能确定比如用一张查找表比计算一个值更快。这种传闻(通常是重复的)可能导致糟糕的设计决策,最终生成效率更慢的代码。
编写好的基准的目的是为了消除所有噪音和开销,并且只测量被测代码。在测量中,噪音源包括时钟分辨率噪音、操作系统后台操作、测试设置/清除、框架开销以及其他不相干的系统行为。
理论上,我们想测量被测代码的执行时间“t”。实际上,我们测到的是“t”与所有噪音的和。

这些影响我们测量的“t”的不相干因素会随时间变化而波动。因此,我们想试图将“t”隔离出来。实现这样的方法是通过多次测量,但只保留最小的总时间。这个最小的总时间必然是受噪音干扰最小且最接近时间“t”的值。
一旦得到这个测量值,在进行隔离就没什么意义了。建立一个基线测试作比较很重要。基线通常应该是一个基于你正要测量出一个解决方案的问题的“经典”或“纯粹”的方案。一旦有了一个基线,你花时间去比较你的算法就有意义。简单地说,你中意的排序算法(fSort)不能在10毫秒内自动排序100万元素。然而,相对于一个经典的排序算法基线如快速排序(qSort),你可以说,fSort处理100万元素比去Sort快50%。这是个很有意义且强大的测量。

实现

Celero大量使用Visual C++和GCC4.7都支持的C++11特性。这对使代码整洁、便携大有帮助。为了使代码更容易采用,用户所需的所有定义都放在一个命名空间为celero的单一头文件:Celero.h里。
Celero.h里包含将每个用户基准用例转换为独特的、具有与之相关测试固件的类(如果有的话),然后登记测试用例到一个工厂。这些宏自动将基线测试用例与相关的测试基准关联起来,这样,在运行时基准相关的数据就可以被算出。这一关联由测试向量来维护。
测试向量利用PImpl惯语来隐藏实现并且保持包含Celero.h的开销降到最小。
Celero将结果输出到命令行。因为颜色是有用的(可能有助于主观因素/结果的可读性),所以std::cout调用了自身之外的一些东西。Console.h定义了一个简单的颜色函数,SetConsoleColor,它可以被celero::print命名空间中的函数用来格式化程序的输出。
测量基准的执行时间位于TestFixturebase类中,而且所有基准的写法都是以此为基础派生出来的。首先,测试夹具(译注:test fixture 是检测被测试系统时所需要的所有东西)的创建代码被执行。接着,测试的开始时间被获取到并以毫秒的形式用一个unsigned long保存起来。这么做是为了减少浮点指针错误。再下一步,指定次数的操作(迭代)被执行。结束时,结束时间被抓取到,测试夹具销毁,本次执行的测量时间被返回,并且这个结果会被保存下来。
无论指定多少个样本,这个循环就像这样重复。如果样本没有指定(为零),那么测试将会重复运行直到一秒钟,或者至少采集了30个样本。当写到代码的这个特定部分时,显然这里存在有一种“if-else”的关系。不管怎么说,大量的代码都是如此重复着“if”与"else"的片段。这里可以使用一个老式的函数,但是利用std::function来定义一个 匿名函数(lambda)再自然不过,这样就可以调用它并使所有的代码整洁。(c++ 11真是一个奇妙的东西)最后,结果被打印到屏幕。

使用代码

Celero 使用 CMake 去提供跨平台构件。由于它使用得是 C++ 11,因而需要请求一个现在流行的编译器(Visual C++ 2012 or GCC 4.7+)。
一旦你的项目中加入了 Celero,你能够创建专门的基准项目和源文件。为了方便,一个头文件和aCELERO_MAINmacro 能提供给 main() ,来帮助你的基准项目自动去执行所有的基准测试。
下面有一个简单的 Celero 基准的例子:

#include 

CELERO_MAIN;

// 运行一个自动的基线。  
// Celero 保证能提供足够的采样来得到一个合理的测量结果
BASELINE(CeleroBenchTest, Baseline, 0, 7100000)
{
    celero::DoNotOptimizeAway(static_cast(sin(3.14159265)));
}

// 运行一个自动测试。  
// Celero 保证能提供足够的采样来得到一个合理的测量结果
BENCHMARK(CeleroBenchTest, Complex1, 0, 7100000)
{
    celero::DoNotOptimizeAway(static_cast(sin(fmod(rand(), 3.14159265))));
}

// 运行一个手动测试。这是对一个样本进行每秒 7100000 次操作。
// Celero 保证能提供足够的采样来得到一个合理的测量结果。
BENCHMARK(CeleroBenchTest, Complex2, 1, 7100000)
{
    celero::DoNotOptimizeAway(static_cast(sin(fmod(rand(), 3.14159265))));
}

// 运行一个手动测试。这是对 60 个样本进行每秒 7100000 次操作。
// Celero 保证能提供足够的采样来得到一个合理的测量结果。
BENCHMARK(CeleroBenchTest, Complex3, 60, 7100000)
{
    celero::DoNotOptimizeAway(static_cast(sin(fmod(rand(), 3.14159265))));
}

这段代码中我们做的第一件事情,就是定义一个BASELINE测试用例。这个模版有四个参数:
BASELINE(GroupName, BaselineName, Samples, Operations)
* GroupName- 基准组的名字。它是用来将运行与结果和它们相应的基线测量收集到一起。
* BaselineName- 为了报表目的的基线的名字。
* Samples- 对测试代码进行给定次数的操作,对这些操作的总的循环运行次数。
* Operations- 你希望对每个样本运行测试代码的次数。

这里的样本与操作是用来测量非常快的代码的。例如,如果你知道基准测试中的代码运行时间小于100毫秒,那么在进行一次测量之前,这个操作次数将会指定代码运行"operations"次。而Samples则定义了要做多少次测量。
Celero允许指定零样本也有助于此。零样本将告诉Celero,基于完成指定次数操作所需要的时间,采集一些具有统计学意义数量的样本。这些数字将会在运行时给出。
celero::DoNotOptimizeAway模版是用来保证优化编译器不会消除你的函数或代码。由于这个功能被用于所有的基准样本以及它们的基线,所以相比较起来其中的额外时间消耗可以忽略不计。
在基线被定义之后,接着被定义的是各种各样的基准。BENCHMARK宏的语法与普通宏的语法完全相同。

结果

示例项目被设置为一旦成功编译就自动执行基准测试代码。在我的PC上运行这个基准测试得到的是以下的输出:

[  CELERO  ]
[==========]
[ STAGE    ] Baselining
[==========]
[ RUN      ] CeleroBenchTest.Baseline -- Auto Run, 7100000 calls per run.
[   AUTO   ] CeleroBenchTest.Baseline -- 30 samples, 7100000 calls per run.
[     DONE ] CeleroBenchTest.Baseline  (0.517049 sec) [7100000 calls in 517049 usec] [0.072824 us/call] [13731773.971132 calls/sec]
[==========]
[ STAGE    ] Benchmarking
[==========]
[ RUN      ] CeleroBenchTest.Complex1 -- Auto Run, 7100000 calls per run.
[   AUTO   ] CeleroBenchTest.Complex1 -- 30 samples, 7100000 calls per run.
[     DONE ] CeleroBenchTest.Complex1  (2.192290 sec) [7100000 calls in 2192290 usec] [0.308773 us/call] [3238622.627481 calls/sec]
[ BASELINE ] CeleroBenchTest.Complex1 4.240004
[ RUN      ] CeleroBenchTest.Complex2 -- 1 run, 7100000 calls per run.
[     DONE ] CeleroBenchTest.Complex2  (2.199197 sec) [7100000 calls in 2199197 usec] [0.309746 us/call] [3228451.111929 calls/sec]
[ BASELINE ] CeleroBenchTest.Complex2 4.253363
[ RUN      ] CeleroBenchTest.Complex3 -- 60 samples, 7100000 calls per run.
[     DONE ] CeleroBenchTest.Complex3  (2.192378 sec) [7100000 calls in 2192378 usec] [0.308786 us/call] [3238492.632201 calls/sec]
[ BASELINE ] CeleroBenchTest.Complex3 4.240175
[==========]
[ STAGE    ] Completed.  4 tests complete.
[==========]

首次运行的测试将作为整个组测试的基线。这个基线显示出它是一个“自动测试”,暗示着由Celero来衡量并决策运行测试代码的次数。这个案例中,在我们的测试里它要运行7100000次代码迭代总共30次。(每调用7100000次测量一次,这样30次以后取最小的时间。)整个测量需要0.517049秒。基于这个结果,可以测量出每次对基准代码的调用需要0.072824毫秒。
在基线测试结束之后,将运行每个多带带的测试。每个测试的运行与测量方式都是相同的,不过有一个额外的度量报告:基线。将它运行基准测试代码所需要的时间与基线进行比较。这里的数据显示,CeleroBenchTest.Complex1比基线运行需要的时间多了4.240004倍。

课外读物

GitHub 项目点击 这里.

基准应该总是要在发行版本中运行。但这绝对不是要求建立一个调试工程,也不是要改变原本的结果。编译器(进行优化)将对你的代码的执行起到非常好的作用。

Celero 在 Doxygen 中存放了 API 文档。

Celero 对每一个基线组固定测试。

当监听到第三个存储槽时,执行代码使基准运行得更快。这是多么有趣的事啊!


原文:Celero - A C++ Benchmark Authoring Library
转载自:开源中国社区--super0555, 竟悟, jimmyjmh

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/8686.html

相关文章

  • MongoDB 资源、、工具、应用程序精选列表中文版

    摘要:推荐阅读资源库工具应用程序精选列表中文版有哪些鲜为人知,但是很有意思的网站一份攻城狮笔记每天搜集上优秀的项目一些有趣的民间故事超好用的谷歌浏览器油猴插件合集目录资源文档文章图书会谈教程更多库工具管理数据部署桌面发展监控应用资源文档介绍文档教 推荐阅读 MongoDB 资源、库、工具、应用程序精选列表中文版 有哪些鲜为人知,但是很有意思的网站? 一份攻城狮笔记 每天搜集 Github ...

    e10101 评论0 收藏0
  • php 学习指南及技术干货

    摘要:安全生成安全的随机数,加密数据,扫描漏洞的库一个兼容标准的过滤器一个生成随机数和字符串的库使用生成随机数的库一个安全库一个纯安全通信库一个简单的键值加密存储库一个结构化的安全层一个试验的面向对象的包装库一个扫描文件安全的库 Security 安全 生成安全的随机数,加密数据,扫描漏洞的库 HTML Purifier-一个兼容标准的HTML过滤器 RandomLib-一个生成随机数和字...

    lifefriend_007 评论0 收藏0
  • php 学习指南及技术干货

    摘要:安全生成安全的随机数,加密数据,扫描漏洞的库一个兼容标准的过滤器一个生成随机数和字符串的库使用生成随机数的库一个安全库一个纯安全通信库一个简单的键值加密存储库一个结构化的安全层一个试验的面向对象的包装库一个扫描文件安全的库 Security 安全 生成安全的随机数,加密数据,扫描漏洞的库 HTML Purifier-一个兼容标准的HTML过滤器 RandomLib-一个生成随机数和字...

    skinner 评论0 收藏0
  • 十个值得一试开源深度学习框架

    摘要:诞生已经有十年之久,但是真正起势得益于去年开源了大量的深度学习模块和扩展。来自一个日本的深度学习创业公司,今年月发布的一个框架。顾名思义,是的深度学习框架,也是较早的商用级别的深度学习开源库。 本周早些时候Google开源了TensorFlow(GitHub),此举在深度学习领域影响巨大,因为Google在人工智能领域的研发成绩斐然,有着雄厚的人才储备,而且Google自己的Gmail和搜索...

    jokester 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<