--- /dev/null
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "lldb",
+ "request": "launch",
+ "name": "Debug",
+ "program": "${workspaceFolder}/build-x86_64/benchmarks",
+ "args": [
+ "-j1"
+ ],
+ "cwd": "${workspaceFolder}"
+ }
+ ]
+}
\ No newline at end of file
"-DCMAKE_TOOLCHAIN_FILE=toolchain-x86_64-linux-gnu.cmake"
],
"cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json",
+ "cmake.generator": "Unix Makefiles",
}
\ No newline at end of file
-cmake_minimum_required(VERSION 3.11.0)
+cmake_minimum_required(VERSION 3.12.0)
project(benchmarks VERSION 0.1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
+find_package(Threads REQUIRED)
+
add_compile_options(-Wall -Wextra -Wimplicit-fallthrough)
-add_executable(benchmarks main.cpp harness.cpp)
+file(GLOB_RECURSE sources
+ RELATIVE ${CMAKE_SOURCE_DIR}
+ CONFIGURE_DEPENDS src/*.cpp src/*.c)
+add_executable(benchmarks "${sources}")
+target_link_libraries(benchmarks Threads::Threads)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
common_cmake_flags = -S .
common_cmake_flags += -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE
+common_cmake_flags += -DCMAKE_BUILD_TYPE=RelWithDebInfo
reset_make_env = "MAKEFLAGS=" "MFLAGS=" "MAKELEVEL=" "MAKE_TERMERR=" "MAKE_TERMOUT="
+++ /dev/null
-#include "harness.h"
-
-void BenchHarnessBase::base_run(
- Config config, void (*fn)(BenchHarnessBase *bench_harness_base,
- std::uint64_t iteration_count))
-{
- // FIXME: finish
-}
\ No newline at end of file
+++ /dev/null
-#pragma once
-
-#include <chrono>
-#include <cstdint>
-#include <optional>
-#include <type_traits>
-#include <utility>
-
-struct Config final
-{
- std::optional<std::uint32_t> thread_count;
- std::optional<std::uint64_t> iteration_count;
-};
-
-template <typename Fn, typename Input,
- typename Output = std::invoke_result_t<Fn, Input>>
-class BenchHarness;
-
-class BenchHarnessBase
-{
- template <typename Fn, typename Input, typename Output>
- friend class BenchHarness;
-
- private:
- void base_run(Config config,
- void (*fn)(BenchHarnessBase *bench_harness_base,
- std::uint64_t iteration_count));
-};
-
-template <typename Fn, typename Input>
-class BenchHarness<Fn, Input, std::invoke_result_t<Fn, Input>> final
- : private BenchHarnessBase
-{
- private:
- Fn fn;
-
- public:
- void run(Config config)
- {
- base_run(config, [](BenchHarnessBase *bench_harness_base,
- std::uint64_t iteration_count) {
- auto &fn = static_cast<BenchHarness *>(bench_harness_base)->fn;
- for (std::uint64_t i = 0; i < iteration_count; i++)
- {
- Input input;
-
- // optimization barrier
- asm("" : : "r"(std::addressof(input)) : "memory");
-
- auto output = fn(input);
-
- // optimization barrier
- asm("" : : "r"(std::addressof(output)) : "memory");
- }
- });
- }
-};
\ No newline at end of file
+++ /dev/null
-#include "harness.h"
-#include <charconv>
-#include <cstdlib>
-#include <functional>
-#include <initializer_list>
-#include <iostream>
-#include <map>
-#include <optional>
-#include <ostream>
-#include <string_view>
-#include <system_error>
-#include <type_traits>
-#include <vector>
-
-using namespace std::literals;
-
-enum class OptionValueKind
-{
- None,
- Required,
-};
-
-class OptionsParser;
-
-struct Option final
-{
- char short_name = '\0';
- std::string_view long_name = "", description = "";
- bool required = false;
- bool all_other_args_not_required = false;
- OptionValueKind value_kind = OptionValueKind::None;
- std::function<void(OptionsParser &parser,
- std::optional<std::string_view> value)>
- parse_value;
- bool has_short_name() const
- {
- return short_name != '\0';
- }
- bool has_long_name() const
- {
- return !long_name.empty();
- }
- friend std::ostream &operator<<(std::ostream &os, const Option &option)
- {
- if (option.has_long_name())
- {
- os << "--" << option.long_name;
- }
- else if (option.has_short_name())
- {
- os << "-" << option.short_name;
- }
- else
- {
- os << "--<unnamed>";
- }
- return os;
- }
-};
-
-class Options final
-{
- friend class OptionsParser;
-
- private:
- std::vector<Option> options;
- std::map<char, std::size_t> short_options;
- std::map<std::string_view, std::size_t> long_options;
-
- public:
- Options(std::initializer_list<Option> options_)
- : options(options_), short_options(), long_options()
- {
- for (std::size_t i = 0; i < options.size(); i++)
- {
- auto &option = options[i];
- if (option.has_short_name())
- short_options[option.short_name] = i;
- if (option.has_long_name())
- long_options[option.long_name] = i;
- }
- }
-
- public:
- std::vector<std::string_view> parse(const char *const *argv) const;
-};
-
-class OptionsParser final
-{
- private:
- const Options &options;
- const char *const *argv;
- std::string_view argv0;
- std::vector<bool> seen_options;
- bool check_required = true;
- std::optional<std::size_t> current_option_index;
-
- private:
- const Option ¤t_option()
- {
- static const Option default_option{};
- if (current_option_index)
- {
- return options.options[*current_option_index];
- }
- else
- {
- return default_option;
- }
- }
-
- public:
- OptionsParser(const Options &options, const char *const *argv)
- : options(options), argv(argv + 1),
- argv0(argv[0] ? argv[0] : "<none>"),
- seen_options(options.options.size(), false), current_option_index()
- {
- if (!argv[0])
- {
- static const char *null[] = {nullptr};
- this->argv = null;
- help_and_exit("missing program name");
- }
- }
-
- private:
- void parse_option(std::size_t option_index, std::string_view prefix,
- std::string_view name,
- std::optional<std::string_view> value)
- {
- auto &option = options.options[option_index];
- switch (option.value_kind)
- {
- case OptionValueKind::None:
- if (value)
- {
- help_and_exit("value not allowed for ", prefix, name);
- }
- break;
- case OptionValueKind::Required:
- if (!value)
- {
- if (*argv)
- {
- value = *argv++;
- }
- else
- {
- help_and_exit("missing value for ", prefix, name);
- }
- }
- break;
- }
- seen_options[option_index] = true;
- if (option.all_other_args_not_required)
- check_required = false;
- current_option_index = option_index;
- option.parse_value(*this, value);
- current_option_index = std::nullopt;
- }
-
- public:
- std::vector<std::string_view> parse()
- {
- std::vector<std::string_view> retval;
- constexpr auto npos = std::string_view::npos;
- while (*argv)
- {
- std::string_view arg = *argv++;
- static constexpr std::string_view long_prefix = "--"sv;
- static constexpr std::string_view short_prefix = "-"sv;
- if (arg.rfind(long_prefix, 0) == 0) // it starts with `--`
- {
- arg.remove_prefix(long_prefix.size());
- auto eq = arg.find('=');
- if (eq == 0)
- {
- eq = npos;
- }
- if (arg.empty()) // just `--`
- {
- while (*argv)
- {
- retval.push_back(*argv++);
- }
- break;
- }
- auto name = arg.substr(0, eq);
- std::optional<std::string_view> value;
- if (eq != npos)
- {
- value = arg.substr(eq + 1);
- }
- auto iter = options.long_options.find(name);
- if (iter == options.long_options.end())
- {
- help_and_exit("unknown option: ", long_prefix, name);
- }
- auto option_index = iter->second;
- parse_option(option_index, long_prefix, name, value);
- continue;
- }
- else if (arg.rfind(short_prefix, 0) == 0) // it starts with `-`
- {
- arg.remove_prefix(short_prefix.size());
- if (arg.empty()) // just `-`
- {
- retval.push_back(short_prefix);
- continue;
- }
- while (!arg.empty())
- {
- auto name = arg.substr(0, 1);
- arg.remove_prefix(1);
- auto iter = options.short_options.find(name[0]);
- if (iter == options.short_options.end())
- {
- help_and_exit("unknown option: ", short_prefix, name);
- }
- auto option_index = iter->second;
- std::optional<std::string_view> value;
- switch (options.options[option_index].value_kind)
- {
- case OptionValueKind::None:
- break;
- case OptionValueKind::Required:
- auto eq = arg.rfind('=', 0);
- if (eq != npos)
- {
- value = arg.substr(eq + 1);
- }
- else if (!arg.empty())
- {
- value = arg;
- }
- arg = "";
- break;
- }
- parse_option(option_index, short_prefix, name, value);
- }
- continue;
- }
- else
- {
- retval.push_back(arg);
- }
- }
- if (check_required)
- {
- for (std::size_t i = 0; i < options.options.size(); i++)
- {
- auto &option = options.options[i];
- if (option.required && !seen_options[i])
- {
- help_and_exit("missing required option ", option);
- }
- }
- }
- return retval;
- }
- template <typename... Options>
- [[noreturn]] void help_and_exit(const Options &...error_msg) const
- {
- auto &os = sizeof...(error_msg) == 0 ? std::cout : std::cerr;
- if (sizeof...(error_msg) != 0)
- {
- ((os << "Error: ") << ... << error_msg) << std::endl;
- }
- os << "Usage: " << argv0;
- for (auto &option : options.options)
- {
- os << " ";
- if (!option.required)
- {
- os << "[";
- }
- auto sep = "";
- if (option.has_short_name())
- {
- os << "-" << option.short_name;
- sep = "|";
- }
- if (option.has_long_name())
- {
- os << sep << "--" << option.long_name;
- }
- switch (option.value_kind)
- {
- case OptionValueKind::None:
- break;
- // TODO: case OptionValueKind::Optional:
- case OptionValueKind::Required:
- os << " <value>";
- break;
- }
- if (!option.required)
- {
- os << "]";
- }
- }
- os << std::endl << "Options:" << std::endl;
- for (auto &option : options.options)
- {
- auto sep = "";
- if (option.has_short_name())
- {
- os << "-" << option.short_name;
- sep = "|";
- }
- if (option.has_long_name())
- {
- os << sep << "--" << option.long_name;
- }
- os << " " << option.description << std::endl;
- }
- std::exit(sizeof...(error_msg) == 0 ? 0 : 1);
- }
- template <typename Int>
- std::enable_if_t<std::is_integral_v<Int>, void> parse_int(
- std::optional<std::string_view> value, Int &i_value)
- {
- i_value = Int();
- if (!value)
- {
- help_and_exit("missing value for ", current_option());
- }
- auto str = *value;
- int base = 10;
- if (0 == str.rfind("0x", 0) || 0 == str.rfind("0X", 0))
- {
- base = 16;
- str.remove_prefix(2);
- }
- else if (0 == str.rfind("0o", 0) || 0 == str.rfind("0O", 0))
- {
- base = 8;
- str.remove_prefix(2);
- }
- else if (0 == str.rfind("0b", 0) || 0 == str.rfind("0B", 0))
- {
- base = 2;
- str.remove_prefix(2);
- }
- std::from_chars_result result = std::from_chars(
- str.data(), str.data() + str.size(), i_value, base);
- if (result.ptr != str.data() + str.size())
- {
- result.ec = std::errc::invalid_argument;
- }
- if (result.ec == std::errc::result_out_of_range)
- {
- help_and_exit("value out of range: ", current_option(), "=",
- *value);
- }
- else if (result.ec != std::errc())
- {
- help_and_exit("invalid value for: ", current_option());
- }
- }
- template <typename Int>
- std::enable_if_t<std::is_integral_v<Int>, void> parse_int(
- std::optional<std::string_view> value, std::optional<Int> &i_value,
- bool required = true)
- {
- if (!required && !value)
- {
- i_value = std::nullopt;
- return;
- }
- i_value.emplace();
- this->parse_int(value, i_value.value());
- }
-};
-
-inline std::vector<std::string_view> Options::parse(
- const char *const *argv) const
-{
- return OptionsParser(*this, argv).parse();
-}
-
-int main(int, char **argv)
-{
- Config config{};
- Options options{
- Option{
- .short_name = 'h',
- .long_name = "help",
- .description = "Display usage and exit.",
- .all_other_args_not_required = true,
- .parse_value = [](OptionsParser &parser,
- auto) { parser.help_and_exit(); },
- },
- Option{.short_name = 'j',
- .long_name = "thread-count",
- .description = "Number of threads to run on",
- .value_kind = OptionValueKind::Required,
- .parse_value =
- [&](OptionsParser &parser, auto value) {
- parser.parse_int(value, config.thread_count);
- }},
- Option{.short_name = 'n',
- .long_name = "iter-count",
- .description = "Number of iterations to run per thread",
- .value_kind = OptionValueKind::Required,
- .parse_value =
- [&](OptionsParser &parser, auto value) {
- parser.parse_int(value, config.iteration_count);
- }},
- };
- OptionsParser parser(options, argv);
- auto args = parser.parse();
- if (!args.empty())
- {
- parser.help_and_exit("unexpected argument");
- }
- // TODO: invoke benchmarks
- return 0;
-}
--- /dev/null
+#include "aarch64_benchmarks.h"
+
+#ifdef __aarch64__
+
+std::vector<Benchmark> aarch64_benchmarks(Config config)
+{
+ std::vector<Benchmark> retval;
+ // TODO: add aarch64 benchmarks
+ (void)config;
+ return retval;
+}
+
+#else
+
+std::vector<Benchmark> aarch64_benchmarks(Config)
+{
+ return {};
+}
+
+#endif
\ No newline at end of file
--- /dev/null
+#pragma once
+
+#include "../harness.h"
+
+std::vector<Benchmark> aarch64_benchmarks(Config config);
\ No newline at end of file
--- /dev/null
+#include "all_benchmarks.h"
+#include "aarch64/aarch64_benchmarks.h"
+#include "common/common_benchmarks.h"
+#include "powerpc64le/powerpc64le_benchmarks.h"
+#include "x86_64/x86_64_benchmarks.h"
+#include <algorithm>
+#include <iterator>
+
+std::vector<Benchmark> all_benchmarks(Config config)
+{
+ std::vector<Benchmark> retval = common_benchmarks(config);
+ {
+ auto benchmarks = x86_64_benchmarks(config);
+ std::move(benchmarks.begin(), benchmarks.end(),
+ std::back_inserter(retval));
+ }
+ {
+ auto benchmarks = aarch64_benchmarks(config);
+ std::move(benchmarks.begin(), benchmarks.end(),
+ std::back_inserter(retval));
+ }
+ {
+ auto benchmarks = powerpc64le_benchmarks(config);
+ std::move(benchmarks.begin(), benchmarks.end(),
+ std::back_inserter(retval));
+ }
+ return retval;
+}
\ No newline at end of file
--- /dev/null
+#pragma once
+
+#include "harness.h"
+
+std::vector<Benchmark> all_benchmarks(Config config);
\ No newline at end of file
--- /dev/null
+#include "c11_atomics_benchmarks.h"
+#include <atomic>
+#include <cstdint>
+#include <memory>
+#include <sstream>
+#include <string_view>
+#include <type_traits>
+
+template <typename T> struct IntTypeName;
+
+template <typename T>
+inline constexpr std::string_view int_type_name = IntTypeName<T>::name;
+
+#define INT_TYPE_NAME(sz, ui, uint) \
+ template <> struct IntTypeName<std::uint##sz##_t> final \
+ { \
+ static constexpr std::string_view name = #ui #sz; \
+ };
+
+INT_TYPE_NAME(8, u, uint)
+INT_TYPE_NAME(16, u, uint)
+INT_TYPE_NAME(32, u, uint)
+INT_TYPE_NAME(64, u, uint)
+INT_TYPE_NAME(8, i, int)
+INT_TYPE_NAME(16, i, int)
+INT_TYPE_NAME(32, i, int)
+INT_TYPE_NAME(64, i, int)
+
+template <std::memory_order order> struct MemoryOrderName;
+
+template <std::memory_order order>
+inline constexpr std::string_view memory_order_name =
+ MemoryOrderName<order>::name;
+
+#define MEMORY_ORDER_NAME(order) \
+ template <> struct MemoryOrderName<std::memory_order_##order> final \
+ { \
+ static constexpr std::string_view name = #order; \
+ };
+
+MEMORY_ORDER_NAME(relaxed)
+MEMORY_ORDER_NAME(acquire)
+MEMORY_ORDER_NAME(release)
+MEMORY_ORDER_NAME(acq_rel)
+MEMORY_ORDER_NAME(seq_cst)
+
+template <typename T> using Buf = std::shared_ptr<std::vector<std::atomic<T>>>;
+
+template <typename Fn, typename T, typename... NameParts>
+static void push_atomic_bench(std::vector<Benchmark> &benches, Config config,
+ Buf<T> buf, Fn fn, NameParts &&...name_parts)
+{
+ auto log2_stride = config.log2_stride;
+ std::size_t index_mask = 1;
+ index_mask <<= config.log2_stride;
+ index_mask <<= config.log2_memory_location_count;
+ index_mask--;
+ push_bench(
+ benches,
+ [buf, fn, index_mask, log2_stride](T input, std::uint64_t iteration,
+ std::uint32_t thread_num) {
+ std::size_t index = iteration;
+ index ^= static_cast<std::size_t>(thread_num) * 0x12345;
+ index <<= log2_stride;
+ index &= index_mask;
+ std::atomic<T> *atomic = &(*buf)[index];
+ input ^= static_cast<T>(iteration);
+ return fn(input, atomic);
+ },
+ T{}, name_parts...);
+}
+
+template <typename T, std::memory_order order>
+static void rmw_benchmarks(std::vector<Benchmark> &benches, Config config,
+ Buf<T> buf)
+{
+ push_atomic_bench(
+ benches, config, buf,
+ [](T input, std::atomic<T> *atomic) {
+ return std::atomic_exchange_explicit(atomic, input, order);
+ },
+ "atomic_exchange_", int_type_name<T>, "_", memory_order_name<order>);
+ push_atomic_bench(
+ benches, config, buf,
+ [](T input, std::atomic<T> *atomic) {
+ return std::atomic_fetch_add_explicit(atomic, input, order);
+ },
+ "atomic_fetch_add_", int_type_name<T>, "_", memory_order_name<order>);
+ push_atomic_bench(
+ benches, config, buf,
+ [](T input, std::atomic<T> *atomic) {
+ return std::atomic_fetch_sub_explicit(atomic, input, order);
+ },
+ "atomic_fetch_sub_", int_type_name<T>, "_", memory_order_name<order>);
+ push_atomic_bench(
+ benches, config, buf,
+ [](T input, std::atomic<T> *atomic) {
+ return std::atomic_fetch_and_explicit(atomic, input, order);
+ },
+ "atomic_fetch_and_", int_type_name<T>, "_", memory_order_name<order>);
+ push_atomic_bench(
+ benches, config, buf,
+ [](T input, std::atomic<T> *atomic) {
+ return std::atomic_fetch_or_explicit(atomic, input, order);
+ },
+ "atomic_fetch_or_", int_type_name<T>, "_", memory_order_name<order>);
+ push_atomic_bench(
+ benches, config, buf,
+ [](T input, std::atomic<T> *atomic) {
+ return std::atomic_fetch_xor_explicit(atomic, input, order);
+ },
+ "atomic_fetch_xor_", int_type_name<T>, "_", memory_order_name<order>);
+}
+
+template <typename T, std::memory_order order>
+static void load_benchmarks(std::vector<Benchmark> &benches, Config config,
+ Buf<T> buf)
+{
+ push_atomic_bench(
+ benches, config, buf,
+ [](T, std::atomic<T> *atomic) {
+ return std::atomic_load_explicit(atomic, order);
+ },
+ "atomic_load_", int_type_name<T>, "_", memory_order_name<order>);
+}
+
+template <typename T, std::memory_order order>
+static void store_benchmarks(std::vector<Benchmark> &benches, Config config,
+ Buf<T> buf)
+{
+ push_atomic_bench(
+ benches, config, buf,
+ [](T input, std::atomic<T> *atomic) {
+ return std::atomic_store_explicit(atomic, input, order);
+ },
+ "atomic_store_", int_type_name<T>, "_", memory_order_name<order>);
+}
+
+template <typename T, std::memory_order succ, std::memory_order fail>
+static void cmp_xchg_benchmarks(std::vector<Benchmark> &benches, Config config,
+ Buf<T> buf)
+{
+ push_atomic_bench(
+ benches, config, buf,
+ [](T input, std::atomic<T> *atomic) {
+ T expected = input >> 1;
+ bool succeeded = std::atomic_compare_exchange_weak_explicit(
+ atomic, &expected, input, succ, fail);
+ return std::pair(expected, succeeded);
+ },
+ "atomic_compare_exchange_weak_", int_type_name<T>, "_",
+ memory_order_name<succ>, "_", memory_order_name<fail>);
+ push_atomic_bench(
+ benches, config, buf,
+ [](T input, std::atomic<T> *atomic) {
+ T expected = input >> 1;
+ bool succeeded = std::atomic_compare_exchange_strong_explicit(
+ atomic, &expected, input, succ, fail);
+ return std::pair(expected, succeeded);
+ },
+ "atomic_compare_exchange_strong_", int_type_name<T>, "_",
+ memory_order_name<succ>, "_", memory_order_name<fail>);
+}
+
+template <typename T>
+static void benchmarks(std::vector<Benchmark> &benches, Config config)
+{
+ std::size_t buf_size = 1;
+ buf_size <<= config.log2_memory_location_count;
+ buf_size <<= config.log2_stride;
+ Buf<T> buf = std::make_shared<std::vector<std::atomic<T>>>(buf_size);
+
+ rmw_benchmarks<T, std::memory_order_relaxed>(benches, config, buf);
+ rmw_benchmarks<T, std::memory_order_acquire>(benches, config, buf);
+ rmw_benchmarks<T, std::memory_order_release>(benches, config, buf);
+ rmw_benchmarks<T, std::memory_order_acq_rel>(benches, config, buf);
+ rmw_benchmarks<T, std::memory_order_seq_cst>(benches, config, buf);
+
+ load_benchmarks<T, std::memory_order_relaxed>(benches, config, buf);
+ load_benchmarks<T, std::memory_order_acquire>(benches, config, buf);
+ load_benchmarks<T, std::memory_order_seq_cst>(benches, config, buf);
+
+ store_benchmarks<T, std::memory_order_relaxed>(benches, config, buf);
+ store_benchmarks<T, std::memory_order_release>(benches, config, buf);
+ store_benchmarks<T, std::memory_order_seq_cst>(benches, config, buf);
+
+ cmp_xchg_benchmarks<T, std::memory_order_relaxed,
+ std::memory_order_relaxed>(benches, config, buf);
+
+ cmp_xchg_benchmarks<T, std::memory_order_acquire,
+ std::memory_order_relaxed>(benches, config, buf);
+ cmp_xchg_benchmarks<T, std::memory_order_acquire,
+ std::memory_order_acquire>(benches, config, buf);
+
+ cmp_xchg_benchmarks<T, std::memory_order_release,
+ std::memory_order_relaxed>(benches, config, buf);
+
+ cmp_xchg_benchmarks<T, std::memory_order_acq_rel,
+ std::memory_order_relaxed>(benches, config, buf);
+ cmp_xchg_benchmarks<T, std::memory_order_acq_rel,
+ std::memory_order_acquire>(benches, config, buf);
+
+ cmp_xchg_benchmarks<T, std::memory_order_seq_cst,
+ std::memory_order_relaxed>(benches, config, buf);
+ cmp_xchg_benchmarks<T, std::memory_order_seq_cst,
+ std::memory_order_acquire>(benches, config, buf);
+ cmp_xchg_benchmarks<T, std::memory_order_seq_cst,
+ std::memory_order_seq_cst>(benches, config, buf);
+}
+
+std::vector<Benchmark> c11_atomics_benchmarks(Config config)
+{
+ std::vector<Benchmark> benches;
+ benchmarks<std::uint8_t>(benches, config);
+ benchmarks<std::uint16_t>(benches, config);
+ benchmarks<std::uint32_t>(benches, config);
+ benchmarks<std::uint64_t>(benches, config);
+ benchmarks<std::int8_t>(benches, config);
+ benchmarks<std::int16_t>(benches, config);
+ benchmarks<std::int32_t>(benches, config);
+ benchmarks<std::int64_t>(benches, config);
+ return benches;
+}
\ No newline at end of file
--- /dev/null
+#pragma once
+
+#include "../../harness.h"
+
+std::vector<Benchmark> c11_atomics_benchmarks(Config config);
\ No newline at end of file
--- /dev/null
+#include "common_benchmarks.h"
+#include "c11_atomics/c11_atomics_benchmarks.h"
+
+std::vector<Benchmark> common_benchmarks(Config config)
+{
+ auto retval = c11_atomics_benchmarks(config);
+ return retval;
+}
\ No newline at end of file
--- /dev/null
+#pragma once
+
+#include "../harness.h"
+
+std::vector<Benchmark> common_benchmarks(Config config);
\ No newline at end of file
--- /dev/null
+#include "harness.h"
+#include <atomic>
+#include <chrono>
+#include <cmath>
+#include <condition_variable>
+#include <functional>
+#include <iostream>
+#include <memory>
+#include <mutex>
+#include <ostream>
+#include <shared_mutex>
+#include <thread>
+#include <variant>
+
+#ifdef NDEBUG // assert needs to work even in release mode
+#undef NDEBUG
+#endif
+#include <cassert>
+
+using std::chrono::steady_clock;
+
+class BenchHarnessBase::ThreadCache final
+{
+ private:
+ std::vector<std::thread> threads;
+ std::shared_mutex state_lock;
+ std::unique_lock<std::shared_mutex> locked_state;
+ std::condition_variable_any cond_var;
+ struct UnlockGuard final
+ {
+ std::shared_mutex &state_lock;
+ UnlockGuard(std::shared_mutex &state_lock) : state_lock(state_lock)
+ {
+ state_lock.unlock();
+ }
+ ~UnlockGuard()
+ {
+ state_lock.lock();
+ }
+ };
+ struct Task final
+ {
+ std::function<void()> fn;
+ };
+ struct ThreadState final
+ {
+ std::unique_ptr<Task> task;
+ std::mutex mutex;
+ };
+ std::vector<std::shared_ptr<ThreadState>> states;
+ bool shutting_down = false;
+ std::atomic_size_t tasks_left_to_drain = 0;
+ void add_thread()
+ {
+ auto thread_state = std::make_shared<ThreadState>();
+ states.push_back(thread_state);
+ threads.push_back(std::thread([this, thread_state]() {
+ auto shared_lock = std::shared_lock(state_lock);
+ while (true)
+ {
+ auto lock = std::unique_lock(thread_state->mutex);
+ auto task = std::move(thread_state->task);
+ lock.unlock();
+ if (task)
+ {
+ task->fn();
+ task.reset();
+ tasks_left_to_drain--;
+ cond_var.notify_all();
+ continue;
+ }
+
+ if (this->shutting_down)
+ return;
+
+ cond_var.wait(shared_lock);
+ }
+ }));
+ }
+
+ public:
+ ThreadCache()
+ {
+ locked_state = std::unique_lock(state_lock);
+ }
+ ThreadCache(const ThreadCache &) = delete;
+ ThreadCache &operator=(const ThreadCache &) = delete;
+ ~ThreadCache()
+ {
+ shutting_down = true;
+ cond_var.notify_all();
+ locked_state.unlock();
+ for (auto &thread : threads)
+ {
+ thread.join();
+ }
+ }
+ static std::shared_ptr<ThreadCache> get()
+ {
+ // weak so it's destroyed before returning from main()
+ static std::weak_ptr<ThreadCache> static_thread_cache;
+
+ std::shared_ptr<ThreadCache> thread_cache = static_thread_cache.lock();
+ if (!thread_cache)
+ {
+ thread_cache = std::make_shared<ThreadCache>();
+ static_thread_cache = thread_cache;
+ }
+ return thread_cache;
+ }
+ static std::shared_ptr<ThreadCache> get(BenchHarnessBase &bhb,
+ std::uint32_t thread_count)
+ {
+ std::shared_ptr<ThreadCache> thread_cache = get();
+ bhb.thread_cache = thread_cache;
+ while (thread_cache->threads.size() < thread_count)
+ thread_cache->add_thread();
+ return thread_cache;
+ }
+ void drain()
+ {
+ while (tasks_left_to_drain > 0)
+ {
+ // unlocks state_lock, allowing all threads to proceed
+ // simultaneously
+ cond_var.wait(locked_state);
+ }
+ }
+ template <typename Fn> void schedule_on(std::uint32_t thread_num, Fn fn)
+ {
+ auto lock = std::unique_lock(states[thread_num]->mutex);
+ assert(!states[thread_num]->task);
+ tasks_left_to_drain++;
+ states[thread_num]->task = std::make_unique<Task>(Task{.fn = fn});
+ cond_var.notify_all();
+ }
+};
+
+struct WriteDuration final
+{
+ std::chrono::duration<double> dur;
+ friend std::ostream &operator<<(std::ostream &os,
+ const WriteDuration &wdur)
+ {
+ double dur = wdur.dur.count();
+ if (!std::isfinite(dur) || std::fabs(dur) > 0.1)
+ {
+ os << dur << " sec";
+ }
+ else if (std::fabs(dur) > 0.1e-3)
+ {
+ os << dur * 1e3 << " ms";
+ }
+ else if (std::fabs(dur) > 0.1e-6)
+ {
+ os << dur * 1e6 << " us";
+ }
+ else if (std::fabs(dur) > 0.1e-9)
+ {
+ os << dur * 1e9 << " ns";
+ }
+ else if (std::fabs(dur) > 0.1e-12)
+ {
+ os << dur * 1e12 << " ps";
+ }
+ else
+ {
+ os << dur << " sec";
+ }
+ return os;
+ }
+};
+
+void BenchHarnessBase::base_run(
+ Config config,
+ void (*fn)(BenchHarnessBase *bench_harness_base,
+ std::uint64_t iteration_count, std::uint32_t thread_num))
+{
+
+ std::uint32_t thread_count =
+ config.thread_count.value_or(std::thread::hardware_concurrency());
+ bool no_threads =
+ thread_count == 0 || (thread_count == 1 && !config.thread_count);
+ if (no_threads)
+ {
+ thread_count = 1;
+ }
+
+ std::vector<steady_clock::duration> elapsed(thread_count);
+ auto run_base = [&](std::uint64_t iteration_count,
+ std::uint32_t thread_num) {
+ auto start_time = steady_clock::now();
+ fn(this, iteration_count, thread_num);
+ auto end_time = steady_clock::now();
+ elapsed[thread_num] = end_time - start_time;
+ };
+ auto run = [&](std::uint64_t iteration_count) {
+ if (no_threads)
+ {
+ return run_base(iteration_count, 0);
+ }
+ auto thread_cache = ThreadCache::get(*this, thread_count);
+ for (std::uint32_t thread_num = 0; thread_num < thread_count;
+ thread_num++)
+ {
+ thread_cache->schedule_on(
+ thread_num, [&run_base, iteration_count, thread_num]() {
+ run_base(iteration_count, thread_num);
+ });
+ }
+ thread_cache->drain();
+ };
+ std::uint64_t iteration_count = 1;
+ if (config.iteration_count)
+ {
+ iteration_count = *config.iteration_count;
+ run(iteration_count);
+ }
+ else
+ {
+ while (true)
+ {
+ run(iteration_count);
+ steady_clock::duration total_elapsed{};
+ for (auto i : elapsed)
+ {
+ total_elapsed += i;
+ }
+ auto target_average_elapsed = std::chrono::milliseconds(500);
+ if (total_elapsed > thread_count * target_average_elapsed)
+ {
+ break;
+ }
+ iteration_count <<= 1;
+ }
+ }
+ steady_clock::duration total_elapsed{};
+ for (std::uint32_t thread_num = 0; thread_num < thread_count; thread_num++)
+ {
+ total_elapsed += elapsed[thread_num];
+ if (thread_count > 1)
+ {
+ auto dur = std::chrono::duration<double>(elapsed[thread_num]);
+ std::cout << "Thread #" << thread_num << " took "
+ << WriteDuration{dur} << " for " << iteration_count
+ << " iterations -- "
+ << WriteDuration{dur / iteration_count} << "/iter.\n";
+ }
+ }
+ auto total = std::chrono::duration<double>(total_elapsed);
+ std::cout << "Average elapsed time: "
+ << WriteDuration{total / thread_count} << " for "
+ << iteration_count << " iterations -- "
+ << WriteDuration{total / thread_count / iteration_count}
+ << "/iter.\n"
+ << std::endl;
+}
+
+std::shared_ptr<void> BenchHarnessBase::get_thread_cache()
+{
+ return ThreadCache::get();
+}
\ No newline at end of file
--- /dev/null
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <optional>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+
+struct Config final
+{
+ std::optional<std::uint32_t> thread_count;
+ std::optional<std::uint64_t> iteration_count;
+ std::uint32_t log2_memory_location_count = 0;
+ std::uint32_t log2_stride = 0;
+ static constexpr std::uint32_t max_sum_log2_mem_loc_count_and_stride = 28;
+};
+
+template <typename Fn, typename Input> class BenchHarness;
+
+class BenchHarnessBase
+{
+ template <typename Fn, typename Input> friend class BenchHarness;
+
+ private:
+ std::shared_ptr<void> thread_cache;
+ class ThreadCache;
+ friend class ThreadCache;
+ void base_run(Config config,
+ void (*fn)(BenchHarnessBase *bench_harness_base,
+ std::uint64_t iteration_count,
+ std::uint32_t thread_num));
+
+ public:
+ static std::shared_ptr<void> get_thread_cache();
+};
+
+template <typename Fn, typename Input>
+class BenchHarness final : private BenchHarnessBase
+{
+ private:
+ Fn fn;
+ Input input;
+
+ public:
+ BenchHarness(Fn fn, Input input)
+ : fn(std::move(fn)), input(std::move(input))
+ {
+ }
+ void run(Config config)
+ {
+ base_run(config, [](BenchHarnessBase *bench_harness_base,
+ std::uint64_t iteration_count,
+ std::uint32_t thread_num) {
+ auto self = static_cast<BenchHarness *>(bench_harness_base);
+ auto &fn = self->fn;
+ // copy for repeatability, also so optimization barrier is on copy,
+ // not self
+ auto input = self->input;
+ for (std::uint64_t i = 0; i < iteration_count; i++)
+ {
+ // optimization barrier
+ asm("" : : "r"(std::addressof(input)) : "memory");
+
+ if constexpr (std::is_void_v<std::invoke_result_t<
+ Fn &, Input, decltype(i),
+ decltype(thread_num)>>)
+ {
+ fn(input, i, thread_num);
+ }
+ else
+ {
+ auto output = fn(input, i, thread_num);
+
+ // optimization barrier
+ asm("" : : "r"(std::addressof(output)) : "memory");
+ }
+ }
+ });
+ }
+};
+
+class Benchmark final
+{
+ private:
+ std::string m_name;
+ std::function<void(Config config)> m_run;
+
+ public:
+ template <typename Fn, typename Input>
+ explicit Benchmark(Fn fn, Input input, std::string name)
+ : m_name(std::move(name)), m_run([fn, input](Config config) {
+ return BenchHarness(std::move(fn), std::move(input)).run(config);
+ })
+ {
+ }
+ void run(Config config)
+ {
+ return m_run(config);
+ }
+ const std::string &name() const
+ {
+ return m_name;
+ }
+};
+
+template <typename Fn, typename Input, typename... NameParts>
+void push_bench(std::vector<Benchmark> &benches, Fn fn, Input input,
+ NameParts &&...name_parts)
+{
+ std::ostringstream os;
+ (os << ... << std::forward<NameParts>(name_parts));
+ benches.push_back(Benchmark(std::move(fn), std::move(input), os.str()));
+}
--- /dev/null
+#include "all_benchmarks.h"
+#include "harness.h"
+#include <charconv>
+#include <cstdlib>
+#include <functional>
+#include <initializer_list>
+#include <iostream>
+#include <limits>
+#include <map>
+#include <optional>
+#include <ostream>
+#include <string_view>
+#include <system_error>
+#include <type_traits>
+#include <unordered_set>
+#include <vector>
+
+using namespace std::literals;
+
+enum class OptionValueKind
+{
+ None,
+ Required,
+};
+
+class OptionsParser;
+
+struct Option final
+{
+ char short_name = '\0';
+ std::string_view long_name = "", description = "";
+ bool required = false;
+ bool all_other_args_not_required = false;
+ OptionValueKind value_kind = OptionValueKind::None;
+ std::function<void(OptionsParser &parser,
+ std::optional<std::string_view> value)>
+ parse_value;
+ bool has_short_name() const
+ {
+ return short_name != '\0';
+ }
+ bool has_long_name() const
+ {
+ return !long_name.empty();
+ }
+ friend std::ostream &operator<<(std::ostream &os, const Option &option)
+ {
+ if (option.has_long_name())
+ {
+ os << "--" << option.long_name;
+ }
+ else if (option.has_short_name())
+ {
+ os << "-" << option.short_name;
+ }
+ else
+ {
+ os << "--<unnamed>";
+ }
+ return os;
+ }
+};
+
+class Options final
+{
+ friend class OptionsParser;
+
+ private:
+ std::vector<Option> options;
+ std::map<char, std::size_t> short_options;
+ std::map<std::string_view, std::size_t> long_options;
+
+ public:
+ Options(std::initializer_list<Option> options_)
+ : options(options_), short_options(), long_options()
+ {
+ for (std::size_t i = 0; i < options.size(); i++)
+ {
+ auto &option = options[i];
+ if (option.has_short_name())
+ short_options[option.short_name] = i;
+ if (option.has_long_name())
+ long_options[option.long_name] = i;
+ }
+ }
+
+ public:
+ std::vector<std::string_view> parse(const char *const *argv) const;
+};
+
+class OptionsParser final
+{
+ private:
+ const Options &options;
+ const char *const *argv;
+ std::string_view argv0;
+ std::vector<bool> seen_options;
+ bool check_required = true;
+ std::optional<std::size_t> current_option_index;
+
+ private:
+ const Option ¤t_option()
+ {
+ static const Option default_option{};
+ if (current_option_index)
+ {
+ return options.options[*current_option_index];
+ }
+ else
+ {
+ return default_option;
+ }
+ }
+
+ public:
+ OptionsParser(const Options &options, const char *const *argv)
+ : options(options), argv(argv + 1),
+ argv0(argv[0] ? argv[0] : "<none>"),
+ seen_options(options.options.size(), false), current_option_index()
+ {
+ if (!argv[0])
+ {
+ static const char *null[] = {nullptr};
+ this->argv = null;
+ help_and_exit("missing program name");
+ }
+ }
+
+ private:
+ void parse_option(std::size_t option_index, std::string_view prefix,
+ std::string_view name,
+ std::optional<std::string_view> value)
+ {
+ auto &option = options.options[option_index];
+ switch (option.value_kind)
+ {
+ case OptionValueKind::None:
+ if (value)
+ {
+ help_and_exit("value not allowed for ", prefix, name);
+ }
+ break;
+ case OptionValueKind::Required:
+ if (!value)
+ {
+ if (*argv)
+ {
+ value = *argv++;
+ }
+ else
+ {
+ help_and_exit("missing value for ", prefix, name);
+ }
+ }
+ break;
+ }
+ seen_options[option_index] = true;
+ if (option.all_other_args_not_required)
+ check_required = false;
+ current_option_index = option_index;
+ option.parse_value(*this, value);
+ current_option_index = std::nullopt;
+ }
+
+ public:
+ std::vector<std::string_view> parse()
+ {
+ std::vector<std::string_view> retval;
+ constexpr auto npos = std::string_view::npos;
+ while (*argv)
+ {
+ std::string_view arg = *argv++;
+ static constexpr std::string_view long_prefix = "--"sv;
+ static constexpr std::string_view short_prefix = "-"sv;
+ if (arg.rfind(long_prefix, 0) == 0) // it starts with `--`
+ {
+ arg.remove_prefix(long_prefix.size());
+ auto eq = arg.find('=');
+ if (eq == 0)
+ {
+ eq = npos;
+ }
+ if (arg.empty()) // just `--`
+ {
+ while (*argv)
+ {
+ retval.push_back(*argv++);
+ }
+ break;
+ }
+ auto name = arg.substr(0, eq);
+ std::optional<std::string_view> value;
+ if (eq != npos)
+ {
+ value = arg.substr(eq + 1);
+ }
+ auto iter = options.long_options.find(name);
+ if (iter == options.long_options.end())
+ {
+ help_and_exit("unknown option: ", long_prefix, name);
+ }
+ auto option_index = iter->second;
+ parse_option(option_index, long_prefix, name, value);
+ continue;
+ }
+ else if (arg.rfind(short_prefix, 0) == 0) // it starts with `-`
+ {
+ arg.remove_prefix(short_prefix.size());
+ if (arg.empty()) // just `-`
+ {
+ retval.push_back(short_prefix);
+ continue;
+ }
+ while (!arg.empty())
+ {
+ auto name = arg.substr(0, 1);
+ arg.remove_prefix(1);
+ auto iter = options.short_options.find(name[0]);
+ if (iter == options.short_options.end())
+ {
+ help_and_exit("unknown option: ", short_prefix, name);
+ }
+ auto option_index = iter->second;
+ std::optional<std::string_view> value;
+ switch (options.options[option_index].value_kind)
+ {
+ case OptionValueKind::None:
+ break;
+ case OptionValueKind::Required:
+ auto eq = arg.rfind('=', 0);
+ if (eq != npos)
+ {
+ value = arg.substr(eq + 1);
+ }
+ else if (!arg.empty())
+ {
+ value = arg;
+ }
+ arg = "";
+ break;
+ }
+ parse_option(option_index, short_prefix, name, value);
+ }
+ continue;
+ }
+ else
+ {
+ retval.push_back(arg);
+ }
+ }
+ if (check_required)
+ {
+ for (std::size_t i = 0; i < options.options.size(); i++)
+ {
+ auto &option = options.options[i];
+ if (option.required && !seen_options[i])
+ {
+ help_and_exit("missing required option ", option);
+ }
+ }
+ }
+ return retval;
+ }
+ template <typename... Options>
+ [[noreturn]] void help_and_exit(const Options &...error_msg) const
+ {
+ auto &os = sizeof...(error_msg) == 0 ? std::cout : std::cerr;
+ if (sizeof...(error_msg) != 0)
+ {
+ ((os << "Error: ") << ... << error_msg) << std::endl;
+ }
+ os << "Usage: " << argv0;
+ for (auto &option : options.options)
+ {
+ os << " ";
+ if (!option.required)
+ {
+ os << "[";
+ }
+ auto sep = "";
+ if (option.has_short_name())
+ {
+ os << "-" << option.short_name;
+ sep = "|";
+ }
+ if (option.has_long_name())
+ {
+ os << sep << "--" << option.long_name;
+ }
+ switch (option.value_kind)
+ {
+ case OptionValueKind::None:
+ break;
+ // TODO: case OptionValueKind::Optional:
+ case OptionValueKind::Required:
+ os << " <value>";
+ break;
+ }
+ if (!option.required)
+ {
+ os << "]";
+ }
+ }
+ os << std::endl << "Options:" << std::endl;
+ for (auto &option : options.options)
+ {
+ auto sep = "";
+ if (option.has_short_name())
+ {
+ os << "-" << option.short_name;
+ sep = "|";
+ }
+ if (option.has_long_name())
+ {
+ os << sep << "--" << option.long_name;
+ }
+ os << " " << option.description << std::endl;
+ }
+ std::exit(sizeof...(error_msg) == 0 ? 0 : 1);
+ }
+ template <typename Int, typename = void> struct ParseIntLimits;
+ template <typename Int>
+ struct ParseIntLimits<Int, std::enable_if_t<std::is_integral_v<Int>>> final
+ {
+ Int min_value = std::numeric_limits<Int>::min();
+ Int max_value = std::numeric_limits<Int>::max();
+ };
+ template <typename Int>
+ std::enable_if_t<std::is_integral_v<Int>, void> parse_int(
+ std::optional<std::string_view> value, Int &i_value,
+ ParseIntLimits<Int> limits = {})
+ {
+ i_value = Int();
+ if (!value)
+ {
+ help_and_exit("missing value for ", current_option());
+ }
+ auto str = *value;
+ int base = 10;
+ if (0 == str.rfind("0x", 0) || 0 == str.rfind("0X", 0))
+ {
+ base = 16;
+ str.remove_prefix(2);
+ }
+ else if (0 == str.rfind("0o", 0) || 0 == str.rfind("0O", 0))
+ {
+ base = 8;
+ str.remove_prefix(2);
+ }
+ else if (0 == str.rfind("0b", 0) || 0 == str.rfind("0B", 0))
+ {
+ base = 2;
+ str.remove_prefix(2);
+ }
+ std::from_chars_result result = std::from_chars(
+ str.data(), str.data() + str.size(), i_value, base);
+ if (result.ptr != str.data() + str.size())
+ {
+ result.ec = std::errc::invalid_argument;
+ }
+ if (result.ec == std::errc())
+ {
+ if (i_value < limits.min_value || i_value > limits.max_value)
+ {
+ result.ec = std::errc::result_out_of_range;
+ }
+ }
+ if (result.ec == std::errc::result_out_of_range)
+ {
+ help_and_exit("value out of range: ", current_option(), "=",
+ *value);
+ }
+ else if (result.ec != std::errc())
+ {
+ help_and_exit("invalid value for: ", current_option());
+ }
+ }
+ template <typename Int>
+ std::enable_if_t<std::is_integral_v<Int>, void> parse_int(
+ std::optional<std::string_view> value, std::optional<Int> &i_value,
+ bool required = true, ParseIntLimits<Int> limits = {})
+ {
+ if (!required && !value)
+ {
+ i_value = std::nullopt;
+ return;
+ }
+ i_value.emplace();
+ this->parse_int(value, i_value.value(), limits);
+ }
+};
+
+inline std::vector<std::string_view> Options::parse(
+ const char *const *argv) const
+{
+ return OptionsParser(*this, argv).parse();
+}
+
+int main(int, char **argv)
+{
+ Config config{};
+ std::optional<std::unordered_set<std::string>> enabled_benchmarks;
+ Options options{
+ Option{
+ .short_name = 'h',
+ .long_name = "help",
+ .description = "Display usage and exit.",
+ .all_other_args_not_required = true,
+ .parse_value = [](OptionsParser &parser,
+ auto) { parser.help_and_exit(); },
+ },
+ Option{.short_name = 'j',
+ .long_name = "thread-count",
+ .description = "Number of threads to run on",
+ .value_kind = OptionValueKind::Required,
+ .parse_value =
+ [&](OptionsParser &parser, auto value) {
+ parser.parse_int(value, config.thread_count);
+ }},
+ Option{.short_name = 'n',
+ .long_name = "iter-count",
+ .description = "Number of iterations to run per thread",
+ .value_kind = OptionValueKind::Required,
+ .parse_value =
+ [&](OptionsParser &parser, auto value) {
+ parser.parse_int(value, config.iteration_count);
+ }},
+ Option{.long_name = "log2-mem-loc-count",
+ .description =
+ "Log base 2 of the number of memory locations to access",
+ .value_kind = OptionValueKind::Required,
+ .parse_value =
+ [&](OptionsParser &parser, auto value) {
+ parser.parse_int(
+ value, config.log2_memory_location_count,
+ {.max_value =
+ Config::max_sum_log2_mem_loc_count_and_stride -
+ config.log2_stride});
+ }},
+ Option{
+ .long_name = "log2-stride",
+ .description =
+ "Log base 2 of the stride used for accessing memory locations",
+ .value_kind = OptionValueKind::Required,
+ .parse_value =
+ [&](OptionsParser &parser, auto value) {
+ parser.parse_int(
+ value, config.log2_stride,
+ {.max_value =
+ Config::max_sum_log2_mem_loc_count_and_stride -
+ config.log2_memory_location_count});
+ }},
+ Option{
+ .short_name = 'b',
+ .long_name = "bench",
+ .description = "List of benchmarks that should be run",
+ .value_kind = OptionValueKind::Required,
+ .parse_value =
+ [&](OptionsParser &, std::optional<std::string_view> value) {
+ if (!enabled_benchmarks)
+ {
+ enabled_benchmarks.emplace();
+ }
+ enabled_benchmarks->emplace(value.value_or(""));
+ }},
+ };
+ OptionsParser parser(options, argv);
+ auto args = parser.parse();
+ if (!args.empty())
+ {
+ parser.help_and_exit("unexpected argument");
+ }
+ auto benchmarks = all_benchmarks(config);
+ if (enabled_benchmarks)
+ {
+ enabled_benchmarks->erase("");
+ enabled_benchmarks->erase("help");
+ enabled_benchmarks->erase("list");
+ if (enabled_benchmarks->empty())
+ {
+ std::cout << "Available Benchmarks:\n";
+ for (auto &benchmark : benchmarks)
+ {
+ std::cout << benchmark.name() << "\n";
+ }
+ std::cout << std::endl;
+ return 0;
+ }
+ std::unordered_set<std::string> unknown_benchmarks =
+ *enabled_benchmarks;
+ for (auto &benchmark : benchmarks)
+ {
+ unknown_benchmarks.erase(benchmark.name());
+ }
+ if (!unknown_benchmarks.empty())
+ {
+ parser.help_and_exit(
+ "unknown benchmark: ", *unknown_benchmarks.begin(),
+ "\nrun with `--bench=list` to see all supported benchmarks.");
+ }
+ }
+ auto thread_cache = BenchHarnessBase::get_thread_cache();
+ for (auto &benchmark : benchmarks)
+ {
+ if (enabled_benchmarks && !enabled_benchmarks->count(benchmark.name()))
+ {
+ continue;
+ }
+ std::cout << "Running: " << benchmark.name() << std::endl;
+ benchmark.run(config);
+ }
+ std::cout << std::endl;
+ return 0;
+}
--- /dev/null
+#include "powerpc64le_benchmarks.h"
+#include <endian.h>
+
+#if defined(__powerpc64__) && BYTE_ORDER == LITTLE_ENDIAN
+
+std::vector<Benchmark> powerpc64le_benchmarks(Config config)
+{
+ std::vector<Benchmark> retval;
+ // TODO: add powerpc64le benchmarks
+ (void)config;
+ return retval;
+}
+
+#else
+
+std::vector<Benchmark> powerpc64le_benchmarks(Config)
+{
+ return {};
+}
+
+#endif
\ No newline at end of file
--- /dev/null
+#pragma once
+
+#include "../harness.h"
+
+std::vector<Benchmark> powerpc64le_benchmarks(Config config);
\ No newline at end of file
--- /dev/null
+#include "x86_64_benchmarks.h"
+
+#ifdef __x86_64__
+
+std::vector<Benchmark> x86_64_benchmarks(Config config)
+{
+ std::vector<Benchmark> retval;
+ // TODO: add x86_64 benchmarks
+ (void)config;
+ return retval;
+}
+
+#else
+
+std::vector<Benchmark> x86_64_benchmarks(Config)
+{
+ return {};
+}
+
+#endif
\ No newline at end of file
--- /dev/null
+#pragma once
+
+#include "../harness.h"
+
+std::vector<Benchmark> x86_64_benchmarks(Config config);
\ No newline at end of file