之前我们的测试用例都只接受一个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);