mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-25 11:56:08 +00:00

Instead of building the benchmarks separately via CMake and running them separately from the test suite, this patch merges the benchmarks into the test suite and handles both uniformly. As a result: - It is now possible to run individual benchmarks like we run tests (e.g. using libcxx-lit), which is a huge quality-of-life improvement. - The benchmarks will be run under exactly the same configuration as the rest of the tests, which is a nice simplification. This does mean that one has to be careful to enable the desired optimization flags when running benchmarks, but that is easy with e.g. `libcxx-lit <...> --param optimization=speed`. - Benchmarks can use the same annotations as the rest of the test suite, such as `// UNSUPPORTED` & friends. When running the tests via `check-cxx`, we only compile the benchmarks because running them would be too time consuming. This introduces a bit of complexity in the testing setup, and instead it would be better to allow passing a --dry-run flag to GoogleBenchmark executables, which is the topic of https://github.com/google/benchmark/issues/1827. I am not really satisfied with the layering violation of adding the %{benchmark_flags} substitution to cmake-bridge, however I believe this can be improved in the future.
214 lines
5.7 KiB
C++
214 lines
5.7 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// UNSUPPORTED: c++03, c++11, c++14, c++17
|
|
|
|
#include <array>
|
|
#include <format>
|
|
#include <random>
|
|
|
|
#include "CartesianBenchmarks.h"
|
|
#include "benchmark/benchmark.h"
|
|
#include "test_macros.h"
|
|
|
|
// Tests the full range of the value.
|
|
template <class T>
|
|
static std::array<T, 1000> generate(std::uniform_int_distribution<T> distribution = std::uniform_int_distribution<T>{
|
|
std::numeric_limits<T>::min(), std::numeric_limits<T>::max()}) {
|
|
std::mt19937 generator;
|
|
std::array<T, 1000> result;
|
|
std::generate_n(result.begin(), result.size(), [&] { return distribution(generator); });
|
|
return result;
|
|
}
|
|
|
|
template <class T>
|
|
static void BM_Basic(benchmark::State& state) {
|
|
std::array data{generate<T>()};
|
|
std::array<char, 100> output;
|
|
|
|
while (state.KeepRunningBatch(data.size()))
|
|
for (auto value : data)
|
|
benchmark::DoNotOptimize(std::format_to(output.begin(), "{}", value));
|
|
}
|
|
BENCHMARK(BM_Basic<uint32_t>);
|
|
BENCHMARK(BM_Basic<int32_t>);
|
|
BENCHMARK(BM_Basic<uint64_t>);
|
|
BENCHMARK(BM_Basic<int64_t>);
|
|
|
|
// Ideally the low values of a 128-bit value are all dispatched to a 64-bit routine.
|
|
#ifndef TEST_HAS_NO_INT128
|
|
template <class T>
|
|
static void BM_BasicLow(benchmark::State& state) {
|
|
using U = std::conditional_t<std::is_signed_v<T>, int64_t, uint64_t>;
|
|
std::array data{
|
|
generate<T>(std::uniform_int_distribution<T>{std::numeric_limits<U>::min(), std::numeric_limits<U>::max()})};
|
|
std::array<char, 100> output;
|
|
|
|
while (state.KeepRunningBatch(data.size()))
|
|
for (auto value : data)
|
|
benchmark::DoNotOptimize(std::format_to(output.begin(), "{}", value));
|
|
}
|
|
BENCHMARK(BM_BasicLow<__uint128_t>);
|
|
BENCHMARK(BM_BasicLow<__int128_t>);
|
|
|
|
BENCHMARK(BM_Basic<__uint128_t>);
|
|
BENCHMARK(BM_Basic<__int128_t>);
|
|
#endif
|
|
|
|
// *** Localization ***
|
|
enum class LocalizationE { False, True };
|
|
struct AllLocalizations : EnumValuesAsTuple<AllLocalizations, LocalizationE, 2> {
|
|
static constexpr const char* Names[] = {"LocFalse", "LocTrue"};
|
|
};
|
|
|
|
template <LocalizationE E>
|
|
struct Localization {};
|
|
|
|
template <>
|
|
struct Localization<LocalizationE::False> {
|
|
static constexpr const char* fmt = "";
|
|
};
|
|
|
|
template <>
|
|
struct Localization<LocalizationE::True> {
|
|
static constexpr const char* fmt = "L";
|
|
};
|
|
|
|
// *** Base ***
|
|
enum class BaseE {
|
|
Binary,
|
|
Octal,
|
|
Decimal,
|
|
Hex,
|
|
HexUpper,
|
|
};
|
|
struct AllBases : EnumValuesAsTuple<AllBases, BaseE, 5> {
|
|
static constexpr const char* Names[] = {"BaseBin", "BaseOct", "BaseDec", "BaseHex", "BaseHexUpper"};
|
|
};
|
|
|
|
template <BaseE E>
|
|
struct Base {};
|
|
|
|
template <>
|
|
struct Base<BaseE::Binary> {
|
|
static constexpr const char* fmt = "b";
|
|
};
|
|
|
|
template <>
|
|
struct Base<BaseE::Octal> {
|
|
static constexpr const char* fmt = "o";
|
|
};
|
|
|
|
template <>
|
|
struct Base<BaseE::Decimal> {
|
|
static constexpr const char* fmt = "d";
|
|
};
|
|
|
|
template <>
|
|
struct Base<BaseE::Hex> {
|
|
static constexpr const char* fmt = "x";
|
|
};
|
|
|
|
template <>
|
|
struct Base<BaseE::HexUpper> {
|
|
static constexpr const char* fmt = "X";
|
|
};
|
|
|
|
// *** Types ***
|
|
enum class TypeE { Int64, Uint64 };
|
|
struct AllTypes : EnumValuesAsTuple<AllTypes, TypeE, 2> {
|
|
static constexpr const char* Names[] = {"Int64", "Uint64"};
|
|
};
|
|
|
|
template <TypeE E>
|
|
struct Type {};
|
|
|
|
template <>
|
|
struct Type<TypeE::Int64> {
|
|
using type = int64_t;
|
|
|
|
static std::array<type, 1000> make_data() { return generate<type>(); }
|
|
};
|
|
|
|
template <>
|
|
struct Type<TypeE::Uint64> {
|
|
using type = uint64_t;
|
|
|
|
static std::array<type, 1000> make_data() { return generate<type>(); }
|
|
};
|
|
|
|
// *** Alignment ***
|
|
enum class AlignmentE { None, Left, Center, Right, ZeroPadding };
|
|
struct AllAlignments : EnumValuesAsTuple<AllAlignments, AlignmentE, 5> {
|
|
static constexpr const char* Names[] = {
|
|
"AlignNone", "AlignmentLeft", "AlignmentCenter", "AlignmentRight", "ZeroPadding"};
|
|
};
|
|
|
|
template <AlignmentE E>
|
|
struct Alignment {};
|
|
|
|
template <>
|
|
struct Alignment<AlignmentE::None> {
|
|
static constexpr const char* fmt = "";
|
|
};
|
|
|
|
template <>
|
|
struct Alignment<AlignmentE::Left> {
|
|
static constexpr const char* fmt = "0<512";
|
|
};
|
|
|
|
template <>
|
|
struct Alignment<AlignmentE::Center> {
|
|
static constexpr const char* fmt = "0^512";
|
|
};
|
|
|
|
template <>
|
|
struct Alignment<AlignmentE::Right> {
|
|
static constexpr const char* fmt = "0>512";
|
|
};
|
|
|
|
template <>
|
|
struct Alignment<AlignmentE::ZeroPadding> {
|
|
static constexpr const char* fmt = "0512";
|
|
};
|
|
|
|
template <class L, class B, class T, class A>
|
|
struct Integral {
|
|
void run(benchmark::State& state) const {
|
|
std::array data{Type<T::value>::make_data()};
|
|
std::array<char, 512> output;
|
|
|
|
while (state.KeepRunningBatch(data.size()))
|
|
for (auto value : data)
|
|
benchmark::DoNotOptimize(std::format_to(output.begin(), std::string_view{fmt.data(), fmt.size()}, value));
|
|
}
|
|
|
|
std::string name() const { return "Integral" + L::name() + B::name() + A::name() + T::name(); }
|
|
|
|
static constexpr std::string make_fmt() {
|
|
return std::string("{:") + Alignment<A::value>::fmt + Localization<L::value>::fmt + Base<B::value>::fmt + "}";
|
|
}
|
|
|
|
static constexpr auto fmt = []() {
|
|
constexpr size_t s = make_fmt().size();
|
|
std::array<char, s> r;
|
|
std::ranges::copy(make_fmt(), r.begin());
|
|
return r;
|
|
}();
|
|
};
|
|
|
|
int main(int argc, char** argv) {
|
|
benchmark::Initialize(&argc, argv);
|
|
if (benchmark::ReportUnrecognizedArguments(argc, argv))
|
|
return 1;
|
|
|
|
makeCartesianProductBenchmark<Integral, AllLocalizations, AllBases, AllTypes, AllAlignments>();
|
|
|
|
benchmark::RunSpecifiedBenchmarks();
|
|
}
|