diff --git a/src/lib/binary-search.hpp b/src/lib/binary-search.hpp new file mode 100644 index 000000000..3835fe432 --- /dev/null +++ b/src/lib/binary-search.hpp @@ -0,0 +1,387 @@ +/* + BINARY-SEARCH.hpp - generic search over continuous domain with a probe predicate + + Copyright (C) Lumiera.org + 2024, 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 binary-search.hpp + ** Textbook implementation of the classical binary search over continuous domain. + ** + ** @see TestChainLoad_test + ** @see SchedulerStress_test + */ + + +#ifndef LIB_BINARY_SEARCH_H +#define LIB_BINARY_SEARCH_H + + +#include "vault/common.hpp" +//#include "test-chain-load.hpp" +//#include "lib/test/transiently.hpp" + +#include "vault/gear/scheduler.hpp" +#include "lib/time/timevalue.hpp" +//#include "lib/iter-explorer.hpp" +#include "lib/meta/function.hpp" +#include "lib/format-string.hpp" +#include "lib/format-cout.hpp"//////////////////////////TODO RLY? +//#include "lib/util.hpp" + +//#include +#include +//#include +//#include +#include +#include +#include + + +namespace lib { + + using util::_Fmt; + using util::min; + using util::max; +// using util::isnil; +// using util::limited; +// using util::unConst; +// using util::toString; +// using util::isLimited; +// using lib::time::Time; +// using lib::time::TimeValue; +// using lib::time::FrameRate; +// using lib::time::Duration; +// using lib::test::Transiently; +// using lib::meta::_FunRet; + +// using std::string; +// using std::function; + using std::make_pair; + using std::make_tuple; +// using std::forward; +// using std::string; +// using std::swap; + using std::vector; + using std::move; + + namespace err = lumiera::error; //////////////////////////TODO RLY? + + namespace { // Default definitions .... + + } + + namespace stress_test_rig { + + + template + inline auto + binarySearch_inner (FUN&& fun, PAR lower, PAR upper, PAR epsilon) + { + ASSERT_VALID_SIGNATURE (FUN, bool(PAR) ); + REQUIRE (lower <= upper); + while ((upper-lower) >= epsilon) + { + PAR div = (lower+upper) / 2; + bool hit = fun(div); + if (hit) + upper = div; + else + lower = div; + } + return (lower+upper)/2; + } + + + template + inline auto + binarySearch_upper (FUN&& fun, PAR lower, PAR upper, PAR epsilon) + { + REQUIRE (lower <= upper); + bool hit = fun(upper); + if (not hit) + {// the upper end breaks contract => search above + PAR len = (upper-lower); + lower = upper - len/10; + upper = lower + 14*len/10; + } + return binarySearch_inner (forward (fun), lower,upper,epsilon); + } + + + template + inline auto + binarySearch (FUN&& fun, PAR lower, PAR upper, PAR epsilon) + { + REQUIRE (lower <= upper); + bool hit = fun(lower); + if (hit) + {// the lower end breaks contract => search below + PAR len = (upper-lower); + upper = lower + len/10; + lower = upper - 14*len/10; + } + return binarySearch_upper (forward (fun), lower,upper,epsilon); + } + + + /** + * Specific stress test scheme to determine the + * »breaking point« where the Scheduler starts to slip + */ + template + class BreakingPointBench + : CONF + { + using TestLoad = decltype(std::declval().testLoad()); + using TestSetup = decltype(std::declval().testSetup (std::declval())); + + struct Res + { + double stressFac{0}; + double percentOff{0}; + double stdDev{0}; + double avgDelta{0}; + double avgTime{0}; + double expTime{0}; + }; + + /** prepare the ScheduleCtx for a specifically parametrised test series */ + void + configureTest (TestSetup& testSetup, double stressFac) + { + testSetup.withLoadTimeBase (CONF::LOAD_BASE) + .withAdaptedSchedule(stressFac, CONF::CONCURRENCY); + } + + /** perform a repetition of test runs and compute statistics */ + Res + runProbes (TestSetup& testSetup, double stressFac) + { + auto sqr = [](auto n){ return n*n; }; + Res res; + auto& [sf,pf,sdev,avgD,avgT,expT] = res; + sf = stressFac; + expT = testSetup.getExpectedEndTime() / 1000; + std::array runTime; + for (uint i=0; i CONF::FAIL_LIMIT); + if (fail) + ++ pf; + showRun(i, delta, runTime[i], runTime[i] > avgT, fail); + } + pf /= CONF::REPETITIONS; + sdev = sqrt (sdev/CONF::REPETITIONS); + showStep(res); + return res; + } + + /** criterion to decide if this test series constitutes a slipped schedule */ + bool + decideBreakPoint (Res& res) + { + return res.percentOff > CONF::TRIGGER_FAIL + and res.stdDev > CONF::TRIGGER_SDEV + and res.avgDelta > CONF::TRIGGER_DELTA; + } + + /** + * invoke a binary search to produce a sequence of test series + * with the goal to narrow down the stressFact where the Schedule slips away. + */ + template + Res + conductBinarySearch (FUN&& runTestCase, vector const& results) + { + double breakPoint = binarySearch_upper (forward (runTestCase), 0.0, CONF::UPPER_STRESS, CONF::EPSILON); + uint s = results.size(); + ENSURE (s >= 2); + Res res; + auto& [sf,pf,sdev,avgD,avgT,expT] = res; + // average data over the last three steps investigated for smoothing + uint points = min (results.size(), 3u); + for (uint i=results.size()-points; iavg? % fail? + _Fmt fmtStep_{ "%4.2f| : ∅Δ=%4.1f±%-4.2f ∅t=%4.1f %s %%%3.1f -- expect:%4.1fms"}; // stress % ∅Δ % σ % ∅t % fail % pecentOff % t-expect + _Fmt fmtResSDv_{"%9s= %5.2f ±%4.2f%s"}; + _Fmt fmtResVal_{"%9s: %5.2f%s"}; + + void + showRun(uint i, double delta, double t, bool over, bool fail) + { + if (CONF::showRuns) + cout << fmtRun_ % i % delta % t % (over? "+":"-") % (fail? "●":"○") + << endl; + } + + void + showStep(Res& res) + { + if (CONF::showStep) + cout << fmtStep_ % res.stressFac % res.avgDelta % res.stdDev % res.avgTime + % (decideBreakPoint(res)? "—◆—":"—◇—") + % res.percentOff % res.expTime + << endl; + } + + void + showRes(Res& res) + { + if (CONF::showRes) + { + cout << fmtResVal_ % "stresFac" % res.stressFac % "" < observations; + auto performEvaluation = [&](double stressFac) + { + configureTest (testSetup, stressFac); + auto res = runProbes (testSetup, stressFac); + observations.push_back (res); + return decideBreakPoint(res); + }; + + Res res = conductBinarySearch (move(performEvaluation), observations); + showRes (res); + showRef (testLoad); + return make_tuple (res.stressFac, res.avgDelta, res.avgTime); + } + }; + }//namespace stress_test_rig + + + + /** configurable template for running Scheduler Stress tests */ + class StressRig + : util::NonCopyable + { + + public: + using usec = std::chrono::microseconds; + + usec LOAD_BASE = 500us; + uint CONCURRENCY = work::Config::getDefaultComputationCapacity(); + double EPSILON = 0.01; ///< error bound to abort binary search + double UPPER_STRESS = 0.6; ///< starting point for the upper limit, likely to fail + double FAIL_LIMIT = 2.0; ///< delta-limit when to count a run as failure + double TRIGGER_FAIL = 0.55; ///< %-fact: criterion-1 failures above this rate + double TRIGGER_SDEV = FAIL_LIMIT; ///< in ms : criterion-2 standard derivation + double TRIGGER_DELTA = 2*FAIL_LIMIT; ///< in ms : criterion-3 delta above this limit + bool showRuns = false; ///< print a line for each individual run + bool showStep = true; ///< print a line for each binary search step + bool showRes = true; ///< print result data + bool showRef = true; ///< calculate single threaded reference time + + static uint constexpr REPETITIONS{20}; + + BlockFlowAlloc bFlow{}; + EngineObserver watch{}; + Scheduler scheduler{bFlow, watch}; + + /** Extension point: build the computation topology for this test */ + auto + testLoad() + { + return TestChainLoad<>{64}; + } + + /** (optional) extension point: base configuration of the test ScheduleCtx */ + template + auto + testSetup (TL& testLoad) + { + return testLoad.setupSchedule(scheduler) + .withJobDeadline(100ms) + .withUpfrontPlanning(); + } + + /** + * Entrance Point: build a stress test measurement setup + * to determine the »breaking point« where the Scheduler is unable + * to keep up with the defined schedule. + * @tparam CONF specialised subclass of StressRig with customisation + * @return a builder to configure and then launch the actual test + */ + template + static auto + with() + { + return stress_test_rig::BreakingPointBench{}; + } + }; + + +} // namespace lib +#endif /*LIB_BINARY_SEARCH_H*/ diff --git a/tests/vault/gear/stress-test-rig.hpp b/tests/vault/gear/stress-test-rig.hpp index 707bff22a..0190bf550 100644 --- a/tests/vault/gear/stress-test-rig.hpp +++ b/tests/vault/gear/stress-test-rig.hpp @@ -90,7 +90,7 @@ #include //#include //#include -//#include +#include #include #include @@ -121,6 +121,7 @@ namespace test { // using std::forward; // using std::string; // using std::swap; + using std::vector; using std::move; namespace err = lumiera::error; //////////////////////////TODO RLY? @@ -131,69 +132,55 @@ namespace test { namespace stress_test_rig { - template - struct _ValidateBinarySearchFun - { - static_assert (not sizeof(P), "Functor unsuitable for binary search. " - "Expected signature pair(PAR)" ); - }; - template - struct _ValidateBinarySearchFun(PAR), PAR> - { - using Result = RES; - }; - template - inline auto - make_binarySearchResults() - { - using Sig = typename lib::meta::_Fun::Sig; - using Res = typename _ValidateBinarySearchFun::Result; - using Data = std::vector>; - return Data{}; - } - - template - inline auto - binarySearch_impl (FUN&& fun, CON results, PAR lower, PAR upper, PAR epsilon) - { - REQUIRE (lower <= upper); - while ((upper-lower) >= epsilon) - { - PAR div = lower + (upper-lower) / 2; - results.emplace_back (fun(div)); - bool hit = results.back().first; - if (hit) - upper = div; - else - lower = div; -cout<<"##################LOOP lower="< Res - conductBinarySearch (FUN&& runTestCase) + conductBinarySearch (FUN&& runTestCase, vector const& results) { - auto results = binarySearch_upper (forward (runTestCase), 0.0, CONF::UPPER_STRESS, CONF::EPSILON); + double breakPoint = binarySearch_upper (forward (runTestCase), 0.0, CONF::UPPER_STRESS, CONF::EPSILON); uint s = results.size(); ENSURE (s >= 2); Res res; @@ -284,7 +271,7 @@ cout<<"##################LOOP lower="<avg? % fail? - _Fmt fmtStep_{ "%4.2f| : ∅Δ=%4.1f±%-4.2f ∅t=%4.1f %%%3.1f -- expect:%4.1fms"}; // stress % ∅Δ % σ % ∅t % fail % t-expect + _Fmt fmtRun_ {"....·%-2d: Δ=%4.1f t=%4.1f %s %s"}; // i % Δ % t % t>avg? % fail? + _Fmt fmtStep_{ "%4.2f| : ∅Δ=%4.1f±%-4.2f ∅t=%4.1f %s %%%3.1f -- expect:%4.1fms"}; // stress % ∅Δ % σ % ∅t % fail % pecentOff % t-expect _Fmt fmtResSDv_{"%9s= %5.2f ±%4.2f%s"}; _Fmt fmtResVal_{"%9s: %5.2f%s"}; @@ -319,7 +305,9 @@ cout<<"##################LOOP lower="< observations; auto performEvaluation = [&](double stressFac) { configureTest (testSetup, stressFac); auto res = runProbes (testSetup, stressFac); - return make_pair (decideBreakPoint(res), res); + observations.push_back (res); + return decideBreakPoint(res); }; - Res res = conductBinarySearch (move (performEvaluation)); + Res res = conductBinarySearch (move(performEvaluation), observations); showRes (res); showRef (testLoad); return make_tuple (res.stressFac, res.avgDelta, res.avgTime); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index ea5cab807..e8e62c79d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -107823,8 +107823,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -107870,19 +107870,30 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + - + + - + + + + + + + + +