0

我最近一直在玩谷歌基准框架,我想测量函数在具有不同统计属性的数据上花费的时间。

为简洁起见,让我们考虑一个最简单的例子:计算向量中奇数元素的总和:

#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

尽管真正的函数做了很多工作,但我在基准测试中观察到了类似的行为。

我怀疑问题出在分支预测中,所以我对此有一些疑问:

  1. 当运行多次迭代时,测量的时间更稳定,但当分支预测还没有那么好时,我们实际上并没有测量“冷启动”。这不是谷歌基准测试的用途吗?
  2. 假设运行大量迭代是不可行的,如何处理上述情况?我们是否运行多个可执行文件?如果我想对两个向量进行不同的基准测试,我该怎么办kN?对于不同的统计分布,我们是否有两个可执行文件,或者对于数据分布和大小的每种组合,我们是否有很多可执行文件?
  3. 我看到很多人说“微基准测试很难”,因为分支预测器总是随着迭代次数而提高。例如,这是一篇博客文章,展示了分支预测器不会随着迭代次数而停止改进。它最终确实进展甚微,但无论如何,我们如何衡量这一点,因为它总是在变得更好?

感谢您的时间,期待任何答复:)

4

0 回答 0