c++性能测试工具:google benchmark入门(二)

上一篇中咱们初步体验了google benchmark的使用,在本文中咱们将更进一步深刻了解google benchmark的经常使用方法。html

本文索引

向测试用例传递参数

以前咱们的测试用例都只接受一个benchmark::State&类型的参数,若是咱们须要给测试用例传递额外的参数呢?c++

举个例子,假如咱们须要实现一个队列,如今有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);

上面的例子展现了如何传递和获取参数:测试

  1. 传递参数使用BENCHMARK宏生成的对象的Arg方法
  2. 传递进来的参数会被放入state对象内部存储,经过range方法获取,调用时的参数0是传入参数的须要,对应第一个参数

Arg方法一次只能传递一个参数,那若是一次想要传递多个参数呢?也很简单:google

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对应传递进来的第二个参数。指针

有一点值得注意,参数传递只能接受整数,若是你但愿使用其余类型的附加参数,就须要另外想些办法了。c++11

简化多个相似测试用例的生成

向测试用例传递参数的最终目的是为了在不编写重复代码的状况下生成多个测试用例,在知道了如何传递参数后你可能会这么写:code

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);

这里咱们生成了三个实例,会产生下面的结果:

pass args

看起来工做良好,是吗?

没错,结果是正确的,可是记得咱们前面说过的吗——不要编写重复的代码!是的,上面咱们手动编写了用例的生成,出现了能够避免的重复。

幸亏ArgArgs会将咱们的测试用例使用的参数进行注册以便产生用例名/参数的新测试用例,而且返回一个指向BENCHMARK宏生成对象的指针,换句话说,若是咱们想要生成仅仅是参数不一样的多个测试的话,只须要链式调用ArgArgs便可:

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);

error

为何会这样呢?那是由于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);

其实这一过程的原理并不复杂,我作个简单的解释:

  1. BENCHMARK宏产生的就是一个benchmark::internal::Benchmark对象而后返回了它的指针
  2. benchmark::internal::Benchmark对象传递参数须要使用Arg和Args等方法
  3. Apply方法会将参数中的函数应用在自身
  4. 咱们在生成器里使用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);

自定义参数的测试结果:

custom_args

至此向测试用例传递参数的方法就所有介绍完了。

下一篇中我会介绍如何将测试用例写成模板,传递参数只能解决一部分重复代码,对于拥有相似方法的不一样待测试类型的测试用例,使用模板将会大大减小咱们没必要要的工做。

相关文章
相关标签/搜索