#c #microbenchmark #google-benchmark
Вопрос:
Недавно я играл с платформой Google benchmark и хочу измерить время, затраченное функцией на данные с различными статистическими свойствами.
Для краткости давайте рассмотрим самый простой из возможных примеров: вычислите сумму нечетных элементов в векторе:
#include <benchmark/benchmark.h>
#include <cstddef>
#include <limits>
#include <random>
#include <vector>
namespace
{
size_t sumOfOddElements(const std::vector<size_t> amp;numbers)
{
size_t result{};
for (const auto amp;number : numbers)
if (number % 2 == 1)
result = number;
return resu<
}
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 amp;state, Args amp;amp;...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
Несмотря на то, что реальная функция выполняет большую работу, я наблюдаю аналогичное поведение в своих тестах.
I suspect the problem is in branch prediction, so I have some questions regarding this:
- When running multiple iterations, the measured time is more stable, but we don’t actually measure the «cold start» when branch prediction is not yet that good. Is it not what google benchmark is for?
- How to deal with the situation above assuming that running lots of iterations is infeasible? Do we run multiple executables? What do I have to do if I want to benchmark both vectors for different
kN
? Do we have two executables for different statistical distributions or do we have lots of executables for each combination of data distribution and size? - Я видел, как многие люди говорили, что «микробанкетирование-это сложно», поскольку предсказатель ветвей всегда улучшается с увеличением числа итераций. Например, вот сообщение в блоге, демонстрирующее, что предсказатель ветвей не перестает улучшаться с увеличением числа итераций. В конце концов, это, конечно, приводит к очень небольшому прогрессу, но в любом случае, как мы можем даже измерить это, поскольку оно всегда становится лучше?
Спасибо за Ваше время, с нетерпением ждем любых ответов 🙂