From 6a7a2832bf9b2d53360384e956185a2f35c1a5fb Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 30 Oct 2023 16:16:54 +0100 Subject: [PATCH] Scheduler: simplify usage of microbenchmark helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit as an aside, the header lib/test/microbenchmark.hpp turns out to be prolific for this kind of investigation. However, it is somewhat obnoxious that the »test subject« must expose the signature . Thus, with some metaprogramming magic, an generic adaptor can be built to accept a range of typical alternatives, and even the quite obvious signature void(void). Since all these will be wrapped directly into a lambda, the optimiser will remove these adaptations altogether. --- src/lib/test/microbenchmark-adaptor.hpp | 171 ++++++++++++++++++++ src/lib/test/microbenchmark.hpp | 27 ++-- tests/vault/gear/scheduler-service-test.cpp | 7 + 3 files changed, 193 insertions(+), 12 deletions(-) create mode 100644 src/lib/test/microbenchmark-adaptor.hpp diff --git a/src/lib/test/microbenchmark-adaptor.hpp b/src/lib/test/microbenchmark-adaptor.hpp new file mode 100644 index 000000000..1178f3d51 --- /dev/null +++ b/src/lib/test/microbenchmark-adaptor.hpp @@ -0,0 +1,171 @@ +/* + MICROBENCHMARK-ADAPTOR.hpp - helper to support microbenchmarks + + Copyright (C) Lumiera.org + 2023, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file microbenchmark-adaptor.hpp + ** Helpers and wrappers so simplify usage of \ref micobenchmark.hpp. + ** Notably the benchmark functions expect the actual »test subject« as a + ** function or lambda with signature `size_t(size_t)`. The argument will be + ** the loop index and the result value will be added into a checksum, which + ** also ensures that the optimiser can not unroll the benchmark loop. + ** However, in practical use this strict requirement for the signature + ** turned out as a nuisance; this header provides some automatic adaption + ** - accept any numeric type as argument + ** - accept any numeric type as checksum contribution (cast to `size_t`) + ** - accept signature `void(void)` + ** + */ + + +#ifndef LIB_TEST_MICROBENCHMARK_ADAPTOR_H +#define LIB_TEST_MICROBENCHMARK_ADAPTOR_H + + +#include "lib/meta/function.hpp" +#include "lib/meta/util.hpp" + +namespace lib { +namespace test{ +namespace microbenchmark { + + using lib::meta::enable_if; + using std::is_arithmetic; + using std::is_same; + using std::__and_; + using std::__not_; + + + /** + * @internal helper to expose the signature `size_t(size_t)` + * by wrapping a given lambda or functor. + */ + template + struct Adaptor + { + static_assert (not sizeof(SIG), "Unable to adapt given functor."); + }; + + template<> + struct Adaptor + { + template + static decltype(auto) + wrap (FUN&& fun) + { + return std::forward(fun); + } + }; + + template<> + struct Adaptor + { + template + static auto + wrap (FUN&& fun) + { + return [functor=std::forward(fun)] + (size_t) + { + functor(); + return size_t(1); + }; + } + }; + + template + struct Adaptor, __not_> + ,is_arithmetic, __not_> + >>> + { + template + static auto + wrap (FUN&& fun) + { + return [functor=std::forward(fun)] + (size_t i) + { + return size_t(functor(i)); + }; + } + }; + + template + struct Adaptor, __not_> + >>> + { + template + static auto + wrap (FUN&& fun) + { + return [functor=std::forward(fun)] + (size_t) + { + return size_t(functor()); + }; + } + }; + + template + struct Adaptor, __not_> + >>> + { + template + static auto + wrap (FUN&& fun) + { + return [functor=std::forward(fun)] + (size_t i) + { + functor(i); + return size_t(1); + }; + } + }; + + + + /** + * Adapter to expose the signature `size_t(size_t)` + * from any suitable source functor or lambda + * @note requirements + * - arity must be either zero or one argument + * - if argument or return type are present, + * they must be plain arithmetic types + * - no references allowed + * - can be `void(void)` + */ + template + inline decltype(auto) + adapted4benchmark (FUN&& fun) + { + static_assert (lib::meta::_Fun(), "Need something function-like."); + static_assert (lib::meta::_Fun::ARITY <=1, "Function with zero or one argument required."); + + using Sig = typename lib::meta::_Fun::Sig; + + return Adaptor::wrap (std::forward (fun)); + } + + + +}}} // namespace lib::test::microbenchmark +#endif /*LIB_TEST_MICROBENCHMARK_ADAPTOR_H*/ diff --git a/src/lib/test/microbenchmark.hpp b/src/lib/test/microbenchmark.hpp index d733f93d7..22d042664 100644 --- a/src/lib/test/microbenchmark.hpp +++ b/src/lib/test/microbenchmark.hpp @@ -59,6 +59,8 @@ #include "lib/sync-barrier.hpp" #include "lib/thread.hpp" +#include "lib/test/microbenchmark-adaptor.hpp" + #include @@ -82,12 +84,12 @@ namespace test{ inline double benchmarkTime (FUN const& invokeTestLoop, const size_t repeatCnt = DEFAULT_RUNS) { - using std::chrono::system_clock;; /////////////////////////////////////////TICKET #886 + using std::chrono::steady_clock; using Dur = std::chrono::duration; - auto start = system_clock::now(); + auto start = steady_clock::now(); invokeTestLoop(); - Dur duration = system_clock::now () - start; + Dur duration = steady_clock::now () - start; return duration.count() / repeatCnt; }; @@ -102,11 +104,11 @@ namespace test{ benchmarkLoop (FUN const& testSubject, const size_t repeatCnt = DEFAULT_RUNS) { // the test subject gets the current loop-index and returns a checksum value - ASSERT_VALID_SIGNATURE (decltype(testSubject), size_t(size_t)); + auto subject4benchmark = microbenchmark::adapted4benchmark (testSubject); size_t checksum{0}; for (size_t i=0; i; // the test subject gets the current loop-index and returns a checksum value - ASSERT_VALID_SIGNATURE (decltype(subject), size_t(size_t)); + auto subject4benchmark = microbenchmark::adapted4benchmark (subject); + using Subject = decltype(subject4benchmark); struct Thread : lib::ThreadJoinable<> { - Thread(FUN const& testSubject, size_t loopCnt, SyncBarrier& testStart) + Thread(Subject const& testSubject, size_t loopCnt, SyncBarrier& testStart) : ThreadJoinable{"Micro-Benchmark" ,[=, &testStart]() // local copy of the test-subject-Functor { testStart.sync(); // block until all threads are ready - auto start = system_clock::now(); + auto start = steady_clock::now(); for (size_t i=0; i < loopCnt; ++i) checksum += testSubject(i); - duration = system_clock::now () - start; + duration = steady_clock::now () - start; }} { } // Note: barrier at begin and join at end both ensure data synchronisation @@ -181,7 +184,7 @@ namespace test{ SyncBarrier testStart{nThreads + 1}; // coordinated start of timing measurement lib::ScopedCollection threads(nThreads); for (size_t n=0; n