/* * This file is open source software, licensed to you under the terms * of the Apache License, Version 2.0 (the "License"). See the NOTICE file * distributed with this work for additional information regarding copyright * ownership. You may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* * Copyright (C) 2018 ScyllaDB Ltd. */ #pragma once #include #include #include #include #include using namespace seastar; namespace perf_tests { namespace internal { struct config; using clock_type = std::chrono::steady_clock; class performance_test { std::string _test_case; std::string _test_group; uint64_t _single_run_iterations = 0; std::atomic _max_single_run_iterations; private: void do_run(const config&); protected: [[gnu::always_inline]] [[gnu::hot]] bool stop_iteration() const { return _single_run_iterations >= _max_single_run_iterations.load(std::memory_order_relaxed); } [[gnu::always_inline]] [[gnu::hot]] void next_iteration() { _single_run_iterations++; } virtual void set_up() = 0; virtual void tear_down() noexcept = 0; virtual future do_single_run() = 0; public: performance_test(const std::string& test_case, const std::string& test_group) : _test_case(test_case) , _test_group(test_group) { } virtual ~performance_test() = default; const std::string& test_case() const { return _test_case; } const std::string& test_group() const { return _test_group; } std::string name() const { return fmt::format("{}.{}", test_group(), test_case()); } void run(const config&); public: static void register_test(std::unique_ptr); }; // Helper for measuring time. // Each microbenchmark can either use the default behaviour which measures // only the start and stop time of the whole run or manually invoke // start_measuring_time() and stop_measuring_time() in order to measure // only parts of each iteration. class time_measurement { clock_type::time_point _run_start_time; clock_type::time_point _start_time; clock_type::duration _total_time; public: [[gnu::always_inline]] [[gnu::hot]] void start_run() { _total_time = { }; auto t = clock_type::now(); _run_start_time = t; _start_time = t; } [[gnu::always_inline]] [[gnu::hot]] clock_type::duration stop_run() { auto t = clock_type::now(); if (_start_time == _run_start_time) { return t - _start_time; } return _total_time; } [[gnu::always_inline]] [[gnu::hot]] void start_iteration() { _start_time = clock_type::now(); } [[gnu::always_inline]] [[gnu::hot]] void stop_iteration() { auto t = clock_type::now(); _total_time += t - _start_time; } }; extern time_measurement measure_time; namespace { template struct do_if_constexpr_ : FalseFn { do_if_constexpr_(TrueFn, FalseFn false_fn) : FalseFn(std::move(false_fn)) { } decltype(auto) operator()() const { // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095 return FalseFn::operator()(0); } }; template struct do_if_constexpr_ : TrueFn { do_if_constexpr_(TrueFn true_fn, FalseFn) : TrueFn(std::move(true_fn)) { } decltype(auto) operator()() const { return TrueFn::operator()(0); } }; template do_if_constexpr_ if_constexpr_(TrueFn&& true_fn, FalseFn&& false_fn) { return do_if_constexpr_(std::forward(true_fn), std::forward(false_fn)); } } template class concrete_performance_test final : public performance_test { compat::optional _test; protected: virtual void set_up() override { _test.emplace(); } virtual void tear_down() noexcept override { _test = compat::nullopt; } [[gnu::hot]] virtual future do_single_run() override { // Redundant 'this->'s courtesy of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61636 return if_constexpr_run())>::value>([&] (auto&&...) { measure_time.start_run(); return do_until([this] { return this->stop_iteration(); }, [this] { this->next_iteration(); return _test->run(); }).then([] { return measure_time.stop_run(); }); }, [&] (auto&&...) { measure_time.start_run(); while (!stop_iteration()) { this->next_iteration(); _test->run(); } return make_ready_future(measure_time.stop_run()); })(); } public: using performance_test::performance_test; }; void register_test(std::unique_ptr); template struct test_registrar { test_registrar(const std::string& test_group, const std::string& test_case) { auto test = std::make_unique>(test_case, test_group); performance_test::register_test(std::move(test)); } }; } [[gnu::always_inline]] inline void start_measuring_time() { internal::measure_time.start_iteration(); } [[gnu::always_inline]] inline void stop_measuring_time() { internal::measure_time.stop_iteration(); } template void do_not_optimize(const T& v) { asm volatile("" : : "r,m" (v)); } } #define PERF_TEST_F(test_group, test_case) \ struct test_##test_group##_##test_case : test_group { \ [[gnu::always_inline]] inline auto run(); \ }; \ static ::perf_tests::internal::test_registrar \ test_##test_group##_##test_case##_registrar(#test_group, #test_case); \ [[gnu::always_inline]] auto test_##test_group##_##test_case::run() #define PERF_TEST(test_group, test_case) \ struct test_##test_group##_##test_case { \ [[gnu::always_inline]] inline auto run(); \ }; \ static ::perf_tests::internal::test_registrar \ test_##test_group##_##test_case##_registrar(#test_group, #test_case); \ [[gnu::always_inline]] auto test_##test_group##_##test_case::run()