c++性能测试工具:google benchmark入门(二)
上一篇中我们初步体验了google benchmark的使用,在本文中我们将更进一步深入了解google benchmark的常用方法。
本文索引
向测试用例传递参数
之前我们的测试用例都只接受一个benchmark::State&类型的参数,如果我们需要给测试用例传递额外的参数呢?
举个例子,假如我们需要实现一个队列,现在有ring buffer和linked list两种实现可选,现在我们要测试两种方案在不同情况下的性能表现:
// 必要的数据结构
#include "ring.h"
#include "linked_ring.h"
// ring buffer的测试
static void bench_array_ring_insert_int_10(benchmark::State& state)
{
auto ring = ArrayRing<int>(10);
for (auto _: state) {
for (int i = 1; i <= 10; ++i) {
ring.insert(i);
}
state.PauseTiming(); // 暂停计时
ring.clear();
state.ResumeTiming(); // 恢复计时
}
}
BENCHMARK(bench_array_ring_insert_int_10);
// linked list的测试
static void bench_linked_queue_insert_int_10(benchmark::State &state)
{
auto ring = LinkedRing<int>{};
for (auto _:state) {
for (int i = 0; i < 10; ++i) {
ring.insert(i);
}
state.PauseTiming();
ring.clear();
state.ResumeTiming();
}
}
BENCHMARK(bench_linked_queue_insert_int_10);
// 还有针对删除的测试,以及针对string的测试,都是高度重复的代码,这里不再罗列
很显然,上面的测试除了被测试类型和插入的数据量之外没有任何区别,如果可以通过传入参数进行控制的话就可以少写大量重复的代码。
编写重复的代码是浪费时间,而且往往意味着你在做一件蠢事,google的工程师们当然早就注意到了这一点。虽然测试用例只能接受一个benchmark::State&类型的参数,但我们可以将参数传递给state对象,然后在测试用例中获取:
static void bench_array_ring_insert_int(benchmark::State& state)
{
auto length = state.range(0);
auto ring = ArrayRing<int>(length);
for (auto _: state) {
for (int i = 1; i <= length; ++i) {
ring.insert(i);
}
state.PauseTiming();
ring.clear();
state.ResumeTiming();
}
}
BENCHMARK(bench_array_ring_insert_int)->Arg(10);
上面的例子展示了如何传递和获取参数:
- 传递参数使用
BENCHMARK宏生成的对象的Arg方法 - 传递进来的参数会被放入state对象内部存储,通过
range方法获取,调用时的参数0是传入参数的需要,对应第一个参数
Arg方法一次只能传递一个参数,那如果一次想要传递多个参数呢?也很简单:
static void bench_array_ring_insert_int(benchmark::State& state)
{
auto ring = ArrayRing<int>(state.range(0));
for (auto _: state) {
for (int i = 1; i <= state.range(1); ++i) {
ring.insert(i);
}
state.PauseTiming();
ring.clear();
state.ResumeTiming();
}
}
BENCHMARK(bench_array_ring_insert_int)->Args({10, 10});
上面的例子没什么实际意义,只是为了展示如何传递多个参数,Args方法接受一个vector对象,所以我们可以使用c++11提供的大括号初始化器简化代码,获取参数依然通过state.range方法,1对应传递进来的第二个参数。
有一点值得注意,参数传递只能接受整数,如果你希望使用其他类型的附加参数,就需要另外想些办法了。
简化多个类似测试用例的生成
向测试用例传递参数的最终目的是为了在不编写重复代码的情况下生成多个测试用例,在知道了如何传递参数后你可能会这么写:
static void bench_array_ring_insert_int(benchmark::State& state)
{
auto length = state.range(0);
auto ring = ArrayRing<int>(length);
for (auto _: state) {
for (int i = 1; i <= length; ++i) {
ring.insert(i);
}
state.PauseTiming();
ring.clear();
state.ResumeTiming();
}
}
// 下面我们生成测试插入10,100,1000次的测试用例
BENCHMARK(bench_array_ring_insert_int)->Arg(10);
BENCHMARK(bench_array_ring_insert_int)->Arg(100);
BENCHMARK(bench_array_ring_insert_int)->Arg(1000);
这里我们生成了三个实例,会产生下面的结果:

看起来工作良好,是吗?
没错,结果是正确的,但是记得我们前面说过的吗——不要编写重复的代码!是的,上面我们手动编写了用例的生成,出现了可以避免的重复。
幸好Arg和Args会将我们的测试用例使用的参数进行注册以便产生用例名/参数的新测试用例,并且返回一个指向BENCHMARK宏生成对象的指针,换句话说,如果我们想要生成仅仅是参数不同的多个测试的话,只需要链式调用Arg和Args即可:
BENCHMARK(bench_array_ring_insert_int)->Arg(10)->Arg(100)->Arg(1000);
结果和上面一样。
但这还不是最优解,我们仍然重复调用了Arg方法,如果我们需要更多用例时就不得不又要做重复劳动了。
对此google benchmark也有解决办法:我们可以使用Range方法来自动生成一定范围内的参数。
先看看Range的原型:
BENCHMAEK(func)->Range(int64_t start, int64_t limit);
start表示参数范围起始的值,limit表示范围结束的值,Range所作用于的是一个_闭区间_。
但是如果我们这样改写代码,是会得到一个错误的测试结果:
BENCHMARK(bench_array_ring_insert_int)->Range(10, 1000);

为什么会这样呢?那是因为Range默认除了start和limit,中间的其余参数都会是某一个基底(base)的幂,基地默认为8,所以我们会看到64和512,它们分别是8的平方和立方。
想要改变这一行为也很简单,只要重新设置基底即可,通过使用RangeMultiplier方法:
BENCHMARK(bench_array_ring_insert_int)->RangeMultiplier(10)->Range(10, 1000);
现在结果恢复如初了。
使用Ranges可以处理多个参数的情况:
BENCHMARK(func)->RangeMultiplier(10)->Ranges({{10, 1000}, {128, 256}});
第一个范围指定了测试用例的第一个传入参数的范围,而第二个范围指定了第二个传入参数可能的值(注意这里不是范围了)。
与下面的代码等价:
BENCHMARK(func)->Args({10, 128})
->Args({100, 128})
->Args({1000, 128})
->Args({10, 256})
->Args({100, 256})
->Args({1000, 256})
实际上就是用生成的第一个参数的范围于后面指定内容的参数做了一个笛卡尔积。
使用参数生成器
如果我想定制没有规律的更复杂的参数呢?这时就需要实现自定义的参数生成器了。
一个参数生成器的签名如下:
void CustomArguments(benchmark::internal::Benchmark* b);
我们在生成器中计算处参数,然后调用benchmark::internal::Benchmark对象的Arg或Args方法像上两节那样传入参数即可。
随后我们使用Apply方法把生成器应用到测试用例上:
BENCHMARK(func)->Apply(CustomArguments);
其实这一过程的原理并不复杂,我做个简单的解释:
BENCHMARK宏产生的就是一个benchmark::internal::Benchmark对象然后返回了它的指针- 向
benchmark::internal::Benchmark对象传递参数需要使用Arg和Args等方法 Apply方法会将参数中的函数应用在自身- 我们在生成器里使用
benchmark::internal::Benchmark对象的指针b的Args等方法传递参数,这时的b其实指向我们的测试用例
到此为止生成器是如何工作的已经一目了然了,当然从上面得出的结论,我们还可以让Apply做更多的事情。
下面看下Apply的具体使用:
// 这次我们生成100,200,...,1000的测试用例,用range是无法生成这些参数的
static void custom_args(benchmark::internal::Benchmark* b)
{
for (int i = 100; i <= 1000; i += 100) {
b->Arg(i);
}
}
BENCHMARK(bench_array_ring_insert_int)->RangeMultiplier(10)->Apply(custom_args);
自定义参数的测试结果:

至此向测试用例传递参数的方法就全部介绍完了。
下一篇中我会介绍如何将测试用例写成模板,传递参数只能解决一部分重复代码,对于拥有类似方法的不同待测试类型的测试用例,使用模板将会大大减少我们不必要的工作。
c++性能测试工具:google benchmark入门(二)的更多相关文章
- c++性能测试工具:google benchmark入门(一)
如果你正在寻找一款c++性能测试工具,那么这篇文章是不容错过的. 市面上的benchmark工具或多或少存在一些使用上的不便,那么是否存在一个使用简便又功能强大的性能测试工具呢?答案是google/b ...
- Web性能测试工具之ab入门篇
1. ab简介 ab全称Apache Bench,是apache附带的一个小工具,它可以同时模拟多个并发请求,测试apache等Web服务器的最大负载压力. 本文通过一个简单的示例,介绍了使用ab进行 ...
- 性能测试工具JMeter 基础(二)—— 主界面介绍
主界面介绍 JMeter的主界面主要分为菜单导航栏.工具栏.计划树标签栏.内容栏 菜单导航栏:全部的功能的都包含在菜单栏中 工具栏:相当于菜单栏常用功能的快捷按钮 计划树标签栏:显示测试用例(计划)相 ...
- c++性能测试工具:google benchmark进阶(一)
这是c++性能测试工具教程的第四篇文章,从本篇开始我将逐步介绍一些性能测试的高级技巧. 前三篇教程可以看这里: c++性能测试工具:google benchmark入门(一) c++性能测试工具:go ...
- Redis性能测试工具benchmark简介
Redis自己提供了一个性能测试工具redis-benchmark.redis-benchmark可以模拟N个机器,同时发送M个请求. 用法:redis-benchmark [-h -h <ho ...
- 性能测试工具 nGrinder 项目剖析及二次开发
转:https://testerhome.com/topics/4225 0.背景 组内需要一款轻量级的性能测试工具,之前考虑过LR(太笨重,单实例,当然它的地位是不容置疑的),阿里云的PTS(htt ...
- google官方建议使用的网站性能测试工具
转自:http://www.laokboke.net/2013/05/12/google-official-recommended-site-performance-testing-tools/ 最近 ...
- Jmeter Web 性能测试入门 (二):Fiddler 抓取 http/https 请求
jmeter自带了拦截request的功能,并且也有对应的tool:badboy 可以用.但由于我经常做移动端的项目,个人还是习惯用fiddler来收集request. 官网下载并安装Fiddler ...
- 开源性能测试工具Locust使用篇(二)
那如何理解Locust和TaskSet这两个类呢? class HttpLocust(Locust) 在Locust类中,具有一个client属性,它对应着虚拟用户作为客户端所具备的请求能力,也就是我 ...
随机推荐
- UVA11090 Going in Cycle!! 【SPFA】
题意:求一个无向图的边权平均值最小的环 思路:假设环中Σwi/t<ans 那变形一下就是Σwi<ans*t → Σ(wi-ans)< 0 这样就可以二分答案做了 #include & ...
- 【DFS+剪枝】Square
https://www.bnuoj.com/v3/contest_show.php?cid=9154#problem/J [题意] 给定n个木棍,问这些木棍能否围成一个正方形 [Accepted] # ...
- UltraEdit-14.10.0.1024版本语法着色配置
用了UltraEdit有段时间了,一直没做语法着色,当做普通文本编辑器使用,这也太委屈这个“神器”了. 今天就让它物尽其用吧.体验一把UltraEdit的语法高亮功能. 参考:http://www.1 ...
- Delphi控件大全
首先来大体上为控件分一下类,以方便我们后面的讨论. 但因为控件的种类太多,所以就粗略的分为如下几个类别∶ ---界面风格类 ---Shell外观类 ---Editor类 ---Gr ...
- 【OpenGL】Shader实例分析(七)- 雪花飘落效果
转发请保持地址:http://blog.csdn.net/stalendp/article/details/40624603 研究了一个雪花飘落效果.感觉挺不错的.分享给大家,效果例如以下: 代码例如 ...
- Swift简单介绍 教程
Swift是什么? Swift是苹果于WWDC 2014公布的编程语言.这里引用The Swift Programming Language的原话: Swift is a new programmi ...
- The 2014 ACM-ICPC Asia Mudanjiang Regional Contest 【部分题解】
2014牡丹江亚洲区域赛邀请赛 B题:图论题目 题解:这里 K题:想法题 分析:两种变化.加入和交换.首先:星号是n的话最少须要的数字是n+1,那么能够首先推断数字够不够,不够的话如今最前面添数字,假 ...
- 重载OverLoad。隐藏new
<1> using System; using System.Collections.Generic; using System.Linq; using System.Text; name ...
- 网络基础笔记——OSI七层模型
OSI七层模型 由于整个网络连接的过程相当复杂,包含硬件.软件数据封包与应用程序的互相链接等等.假设想要写一支将联网所有功能都串连在一块的程序.那么当某个小环节出现故障时,整仅仅程序都须要改写.所以我 ...
- [概念理解] UML类建模
Class Diagram Figure 4.30 Elements of the class diagram 关联,多重性: 聚合aggregation. In class diagrams, as ...