diff --git a/src/lib/binary-search.hpp b/src/lib/binary-search.hpp index 3835fe432..2c43ad015 100644 --- a/src/lib/binary-search.hpp +++ b/src/lib/binary-search.hpp @@ -22,8 +22,19 @@ /** @file binary-search.hpp ** Textbook implementation of the classical binary search over continuous domain. + ** The domain is given by its lower and upper end points. Within this domain, + ** a _breaking point_ is located, where the result of a _probe predicate_ + ** flips from `false` to `true`. For the core search, the _invariant_ + ** is assumed, implying that the `predicate(lower) ≡ false` and + ** `predicate(upper) ≡ true`. ** - ** @see TestChainLoad_test + ** For good convergence, it is advisable to enter the search with rather tight + ** bounds. For the case that it's not clear if the invariant holds for both ends, + ** two alternative entrance points are provided, which check the condition on the + ** interval ends and possibly shift and expand the search domain in case the + ** assumption is broken. + ** + ** @see stress-test-rig.hpp ** @see SchedulerStress_test */ @@ -32,355 +43,77 @@ #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; + using std::forward; - namespace err = lumiera::error; //////////////////////////TODO RLY? - - namespace { // Default definitions .... - + /** binary search: actual search loop + * - search until (upper-lower) < epsilon + * - the \a FUN performs the actual test + * - the goal is to narrow down the breaking point + * @note `fun(lower)` must be `false` and + * `fun(upper)` must be `true` + */ + 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; } - 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 + + /** entrance point to binary search to ensure the upper point + * indeed fulfils the test. If this is not the case, the search domain + * is shifted up, but also expanded so that the given upper point is + * still located within, but close to the lower end. + * @note `fun(lower)` must be `false` + */ + 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); + } - - /** 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{}; - } - }; + 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); + } } // namespace lib diff --git a/tests/vault/gear/stress-test-rig.hpp b/tests/vault/gear/stress-test-rig.hpp index 0190bf550..60d6496f1 100644 --- a/tests/vault/gear/stress-test-rig.hpp +++ b/tests/vault/gear/stress-test-rig.hpp @@ -67,6 +67,7 @@ ** ** @see TestChainLoad_test ** @see SchedulerStress_test + ** @see binary-search.hpp */ @@ -75,6 +76,7 @@ #include "vault/common.hpp" +#include "lib/binary-search.hpp" //#include "test-chain-load.hpp" //#include "lib/test/transiently.hpp" @@ -132,58 +134,6 @@ namespace test { 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 @@ -262,7 +212,9 @@ namespace test { Res conductBinarySearch (FUN&& runTestCase, vector const& results) { - double breakPoint = binarySearch_upper (forward (runTestCase), 0.0, CONF::UPPER_STRESS, CONF::EPSILON); + double breakPoint = lib::binarySearch_upper (forward (runTestCase) + , 0.0, CONF::UPPER_STRESS + , CONF::EPSILON); uint s = results.size(); ENSURE (s >= 2); Res res; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index e8e62c79d..6a71e352f 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -107505,16 +107505,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

weil die Invariante eben auch erfüllt ist, wenn man hinter dem Ende steht; insofern ist die Auswertung ja tatsächlich »schleppend«, d.h. man gibt immer die vorhergehende Gruppe aus

- -
+
@@ -107706,16 +107703,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

und withAdaptedSchedule muß man explizit aufrufen (wegen Stress-Faktor)

- -
+
@@ -107803,23 +107797,20 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

das ist aber nicht klar genug ausgeprägt, um daraus ein Kriterium zu machen

- -
+
- - + + @@ -107833,8 +107824,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -107855,16 +107846,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Mittelpunkt auswerten: test(m)

- -
+ @@ -107896,6 +107884,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + +