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

至此向測試用例傳遞參數的方法就所有介紹完了。

下一篇中我會介紹如何將測試用例寫成模板,傳遞參數只能解決一部分重複代碼,對於擁有相似方法的不一樣待測試類型的測試用例,使用模板將會大大減小咱們沒必要要的工做。

相關文章
相關標籤/搜索