我最近一直在玩谷歌基准框架,我想测量函数在具有不同统计属性的数据上花费的时间。
为简洁起见,让我们考虑一个最简单的例子:计算向量中奇数元素的总和:
#include <benchmark/benchmark.h>
#include <cstddef>
#include <limits>
#include <random>
#include <vector>
namespace
{
size_t sumOfOddElements(const std::vector<size_t> &numbers)
{
size_t result{};
for (const auto &number : numbers)
if (number % 2 == 1)
result += number;
return result;
}
std::vector<size_t> produceElements(size_t n, double oddProbability)
{
std::mt19937 gen{};
gen.seed(123);
std::uniform_real_distribution<double> coin{ 0.0, 1.0 };
std::uniform_int_distribution<size_t> numbers{
0, std::numeric_limits<size_t>::max() / 2
};
std::vector<size_t> elements;
elements.reserve(n);
for (size_t i{}; i < n; ++i)
{
auto coinValue = coin(gen);
auto number = numbers(gen);
// odd
if (coinValue < oddProbability)
number = number * 2 - 1;
// even
else
number = number * 2;
elements.push_back(number);
}
return elements;
}
constexpr size_t kN{ 2'000'000 };
const std::vector<size_t> lotsOfOddElements{ produceElements(kN, 0.9) };
const std::vector<size_t> fewOddElements{ produceElements(kN, 0.1) };
template <class... Args>
void BM_sumOfOddElements(benchmark::State &state, Args &&...args)
{
for (auto _ : state)
benchmark::DoNotOptimize(sumOfOddElements(std::forward<Args>(args)...));
}
} // namespace
BENCHMARK_CAPTURE(BM_sumOfOddElements, fewOddElements, fewOddElements)
->Unit(benchmark::kMillisecond)
->Iterations(10);
BENCHMARK_CAPTURE(BM_sumOfOddElements, lotsOfOddElements, lotsOfOddElements)
->Unit(benchmark::kMillisecond)
->Iterations(10);
BENCHMARK_MAIN();
尽管函数非常简单,但我限制它只运行 10 次迭代,因为我实际想要分析的函数成本很高(每次迭代大约 10 秒)。
因为迭代次数不够大,我遇到了不同的时间和不同的基准测试顺序:
----------------------------------------------------------------------------------------------
Benchmark Time CPU Iterations
----------------------------------------------------------------------------------------------
BM_sumOfOddElements/fewOddElements/iterations:10 1.58 ms 1.58 ms 10
BM_sumOfOddElements/lotsOfOddElements/iterations:10 1.63 ms 1.63 ms 10
----------------------------------------------------------------------------------------------
Benchmark Time CPU Iterations
----------------------------------------------------------------------------------------------
BM_sumOfOddElements/lotsOfOddElements/iterations:10 1.56 ms 1.57 ms 10
BM_sumOfOddElements/fewOddElements/iterations:10 1.72 ms 1.72 ms 10
尽管真正的函数做了很多工作,但我在基准测试中观察到了类似的行为。
我怀疑问题出在分支预测中,所以我对此有一些疑问:
- 当运行多次迭代时,测量的时间更稳定,但当分支预测还没有那么好时,我们实际上并没有测量“冷启动”。这不是谷歌基准测试的用途吗?
- 假设运行大量迭代是不可行的,如何处理上述情况?我们是否运行多个可执行文件?如果我想对两个向量进行不同的基准测试,我该怎么办
kN
?对于不同的统计分布,我们是否有两个可执行文件,或者对于数据分布和大小的每种组合,我们是否有很多可执行文件? - 我看到很多人说“微基准测试很难”,因为分支预测器总是随着迭代次数而提高。例如,这是一篇博客文章,展示了分支预测器不会随着迭代次数而停止改进。它最终确实进展甚微,但无论如何,我们如何衡量这一点,因为它总是在变得更好?
感谢您的时间,期待任何答复:)