Scheduler-test: resolve inconsistency in time accounting for instrumentation
Basically users are free to place the measurement calls to their liking. This implies that bracketed measurement intervals can be defined overlapping even within a single thread, thereby accounting the overlapping time interval several times. However, for the time spent per thread, only actual thread activity should be counted, disregarding overlaps. Thus introduce a new aggregate, ''active time'', which is the sum of all thread times. As an aside, do not need explicit randomness for the simple two-thread test case — timings are random anyway... + bugfix for out-of-bounds access
This commit is contained in:
parent
9f0878f885
commit
d0c1017580
3 changed files with 74 additions and 31 deletions
|
|
@ -155,7 +155,7 @@ namespace lib {
|
||||||
{
|
{
|
||||||
REQUIRE (cnt);
|
REQUIRE (cnt);
|
||||||
for (Sequence& s : rec_)
|
for (Sequence& s : rec_)
|
||||||
s.reserve (cnt);
|
s.reserve (2*cnt);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,14 +173,15 @@ namespace lib {
|
||||||
{
|
{
|
||||||
size_t eventCnt{0};
|
size_t eventCnt{0};
|
||||||
size_t activationCnt{0};
|
size_t activationCnt{0};
|
||||||
double cumulatedTime{0};
|
double cumulatedTime{0}; ///< aggregated time over all cases
|
||||||
double coveredTime{0};
|
double activeTime{0}; ///< compounded time of thread activity
|
||||||
double avgConcurrency{0};
|
double coveredTime{0}; ///< overall timespan of observation
|
||||||
|
double avgConcurrency{0}; ///< amortised concurrency in timespan
|
||||||
|
|
||||||
vector<size_t> caseCntr{};
|
vector<size_t> caseCntr{}; ///< counting activations per case
|
||||||
vector<size_t> thrdCntr{};
|
vector<size_t> thrdCntr{}; ///< counting activations per thread
|
||||||
vector<double> caseTime{};
|
vector<double> caseTime{}; ///< aggregated time per case
|
||||||
vector<double> thrdTime{};
|
vector<double> thrdTime{}; ///< time of activity per thread
|
||||||
vector<double> concTime{};
|
vector<double> concTime{};
|
||||||
|
|
||||||
template<typename VAL>
|
template<typename VAL>
|
||||||
|
|
@ -235,12 +236,11 @@ namespace lib {
|
||||||
);
|
);
|
||||||
|
|
||||||
int active{0};
|
int active{0};
|
||||||
size_t numCases = std::numeric_limits<uint8_t>::max()+1;
|
vector<int> active_case;
|
||||||
vector<int> active_case(numCases);
|
|
||||||
vector<int> active_thrd(numThreads);
|
vector<int> active_thrd(numThreads);
|
||||||
stat.thrdCntr.resize (numThreads);
|
stat.thrdCntr.resize (numThreads);
|
||||||
stat.thrdTime.resize (numThreads);
|
stat.thrdTime.resize (numThreads);
|
||||||
stat.concTime.resize (numThreads);
|
stat.concTime.resize (numThreads+1); // also accounting for idle times in range
|
||||||
|
|
||||||
// Integrate over the timeline...
|
// Integrate over the timeline...
|
||||||
// - book the preceding interval length into each affected partial sum
|
// - book the preceding interval length into each affected partial sum
|
||||||
|
|
@ -250,6 +250,7 @@ namespace lib {
|
||||||
{
|
{
|
||||||
if (event.caseID >= stat.caseCntr.size())
|
if (event.caseID >= stat.caseCntr.size())
|
||||||
{
|
{
|
||||||
|
active_case .resize (event.caseID+1);
|
||||||
stat.caseCntr.resize (event.caseID+1);
|
stat.caseCntr.resize (event.caseID+1);
|
||||||
stat.caseTime.resize (event.caseID+1);
|
stat.caseTime.resize (event.caseID+1);
|
||||||
}
|
}
|
||||||
|
|
@ -258,7 +259,8 @@ namespace lib {
|
||||||
for (uint i=0; i < stat.caseCntr.size(); ++i)
|
for (uint i=0; i < stat.caseCntr.size(); ++i)
|
||||||
stat.caseTime[i] += active_case[i] * timeSlice.count();
|
stat.caseTime[i] += active_case[i] * timeSlice.count();
|
||||||
for (uint i=0; i < numThreads; ++i)
|
for (uint i=0; i < numThreads; ++i)
|
||||||
stat.thrdTime[i] += active_thrd[i] * timeSlice.count();
|
if (active_thrd[i]) // note: counting activity per thread, without overlapping cases
|
||||||
|
stat.thrdTime[i] += timeSlice.count();
|
||||||
size_t concurr = explore(active_thrd).filter([](int a){ return 0 < a; }).count();
|
size_t concurr = explore(active_thrd).filter([](int a){ return 0 < a; }).count();
|
||||||
ENSURE (concurr <= numThreads);
|
ENSURE (concurr <= numThreads);
|
||||||
stat.avgConcurrency += concurr * timeSlice.count(); // contribution for weighted average
|
stat.avgConcurrency += concurr * timeSlice.count(); // contribution for weighted average
|
||||||
|
|
@ -289,6 +291,7 @@ namespace lib {
|
||||||
ENSURE (0 < stat.activationCnt);
|
ENSURE (0 < stat.activationCnt);
|
||||||
ENSURE (stat.eventCnt % 2 == 0);
|
ENSURE (stat.eventCnt % 2 == 0);
|
||||||
stat.avgConcurrency /= stat.coveredTime; // time used as weight sum
|
stat.avgConcurrency /= stat.coveredTime; // time used as weight sum
|
||||||
|
stat.activeTime = explore(stat.thrdTime).resultSum();
|
||||||
return stat;
|
return stat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,20 @@
|
||||||
using util::isLimited;
|
using util::isLimited;
|
||||||
using std::this_thread::sleep_for;
|
using std::this_thread::sleep_for;
|
||||||
using std::chrono_literals::operator ""ms;
|
using std::chrono_literals::operator ""ms;
|
||||||
|
using std::chrono_literals::operator ""us;
|
||||||
using std::chrono::microseconds;
|
using std::chrono::microseconds;
|
||||||
|
|
||||||
|
|
||||||
namespace lib {
|
namespace lib {
|
||||||
namespace test{
|
namespace test{
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
inline bool
|
||||||
|
isNumEq (double d1, double d2)
|
||||||
|
{
|
||||||
|
return 0.001 > abs(d1-d2);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -120,6 +128,7 @@ namespace test{
|
||||||
auto stat = watch.evaluate();
|
auto stat = watch.evaluate();
|
||||||
SHOW_EXPR(stat.cumulatedTime);
|
SHOW_EXPR(stat.cumulatedTime);
|
||||||
SHOW_EXPR(stat.coveredTime);
|
SHOW_EXPR(stat.coveredTime);
|
||||||
|
SHOW_EXPR(stat.activeTime);
|
||||||
SHOW_EXPR(stat.eventCnt);
|
SHOW_EXPR(stat.eventCnt);
|
||||||
SHOW_EXPR(stat.activationCnt);
|
SHOW_EXPR(stat.activationCnt);
|
||||||
SHOW_EXPR(stat.cntCase(0));
|
SHOW_EXPR(stat.cntCase(0));
|
||||||
|
|
@ -136,7 +145,7 @@ SHOW_EXPR(stat.cntThread(0));
|
||||||
SHOW_EXPR(stat.cntThread(1));
|
SHOW_EXPR(stat.cntThread(1));
|
||||||
SHOW_EXPR(stat.timeThread(0));
|
SHOW_EXPR(stat.timeThread(0));
|
||||||
SHOW_EXPR(stat.timeThread(1));
|
SHOW_EXPR(stat.timeThread(1));
|
||||||
CHECK (isLimited (15500, stat.cumulatedTime, 17500)); // ≈ 16ms
|
CHECK (isLimited (15500, stat.cumulatedTime, 17800)); // ≈ 16ms
|
||||||
CHECK (isLimited ( 8500, stat.coveredTime, 10000)); // ≈ 9ms
|
CHECK (isLimited ( 8500, stat.coveredTime, 10000)); // ≈ 9ms
|
||||||
CHECK (10== stat.eventCnt);
|
CHECK (10== stat.eventCnt);
|
||||||
CHECK (5 == stat.activationCnt);
|
CHECK (5 == stat.activationCnt);
|
||||||
|
|
@ -152,9 +161,10 @@ SHOW_EXPR(stat.timeThread(1));
|
||||||
CHECK (0 == stat.timeCase(4));
|
CHECK (0 == stat.timeCase(4));
|
||||||
CHECK (5 == stat.cntThread(0));
|
CHECK (5 == stat.cntThread(0));
|
||||||
CHECK (0 == stat.cntThread(1));
|
CHECK (0 == stat.cntThread(1));
|
||||||
CHECK (stat.cumulatedTime == stat.timeThread(0));
|
CHECK (stat.activeTime == stat.timeThread(0));
|
||||||
CHECK (0 == stat.timeThread(1));
|
CHECK (0 == stat.timeThread(1));
|
||||||
CHECK (1 > abs(stat.cumulatedTime - (stat.timeCase(1) + stat.timeCase(2) + stat.timeCase(3))));
|
CHECK (isNumEq (stat.activeTime, stat.coveredTime));
|
||||||
|
CHECK (isNumEq (stat.cumulatedTime , stat.timeCase(1) + stat.timeCase(2) + stat.timeCase(3)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -165,18 +175,16 @@ SHOW_EXPR(stat.timeThread(1));
|
||||||
verify_concurrencyStatistic()
|
verify_concurrencyStatistic()
|
||||||
{
|
{
|
||||||
MARK_TEST_FUN
|
MARK_TEST_FUN
|
||||||
const size_t CONCURR = std::thread::hardware_concurrency();
|
|
||||||
|
|
||||||
IncidenceCount watch;
|
IncidenceCount watch;
|
||||||
watch.expectThreads(CONCURR)
|
watch.expectThreads(2)
|
||||||
.expectIncidents(5000);
|
.expectIncidents(2);
|
||||||
|
|
||||||
auto act = [&]{ // two nested activities with random delay
|
auto act = [&]{ // two nested activities
|
||||||
uint delay = 100 + rand() % 800;
|
|
||||||
watch.markEnter();
|
watch.markEnter();
|
||||||
sleep_for (microseconds(delay));
|
sleep_for (600us);
|
||||||
watch.markEnter(2);
|
watch.markEnter(2);
|
||||||
sleep_for (microseconds(delay));
|
sleep_for (200us);
|
||||||
watch.markLeave(2);
|
watch.markLeave(2);
|
||||||
watch.markLeave();
|
watch.markLeave();
|
||||||
};
|
};
|
||||||
|
|
@ -196,6 +204,7 @@ SHOW_EXPR(stat.timeThread(1));
|
||||||
auto stat = watch.evaluate();
|
auto stat = watch.evaluate();
|
||||||
SHOW_EXPR(runTime)
|
SHOW_EXPR(runTime)
|
||||||
SHOW_EXPR(stat.cumulatedTime);
|
SHOW_EXPR(stat.cumulatedTime);
|
||||||
|
SHOW_EXPR(stat.activeTime);
|
||||||
SHOW_EXPR(stat.coveredTime);
|
SHOW_EXPR(stat.coveredTime);
|
||||||
SHOW_EXPR(stat.eventCnt);
|
SHOW_EXPR(stat.eventCnt);
|
||||||
SHOW_EXPR(stat.activationCnt);
|
SHOW_EXPR(stat.activationCnt);
|
||||||
|
|
@ -217,8 +226,10 @@ SHOW_EXPR(stat.avgConcurrency);
|
||||||
SHOW_EXPR(stat.timeAtConc(0));
|
SHOW_EXPR(stat.timeAtConc(0));
|
||||||
SHOW_EXPR(stat.timeAtConc(1));
|
SHOW_EXPR(stat.timeAtConc(1));
|
||||||
SHOW_EXPR(stat.timeAtConc(2));
|
SHOW_EXPR(stat.timeAtConc(2));
|
||||||
|
SHOW_EXPR(stat.timeAtConc(3));
|
||||||
CHECK (runTime > stat.coveredTime);
|
CHECK (runTime > stat.coveredTime);
|
||||||
CHECK (stat.coveredTime < stat.cumulatedTime);
|
CHECK (stat.coveredTime < stat.cumulatedTime);
|
||||||
|
CHECK (stat.activeTime <= stat.cumulatedTime);
|
||||||
CHECK (8 == stat.eventCnt);
|
CHECK (8 == stat.eventCnt);
|
||||||
CHECK (4 == stat.activationCnt);
|
CHECK (4 == stat.activationCnt);
|
||||||
CHECK (2 == stat.cntCase(0));
|
CHECK (2 == stat.cntCase(0));
|
||||||
|
|
@ -228,18 +239,19 @@ SHOW_EXPR(stat.timeAtConc(2));
|
||||||
CHECK (2 == stat.cntThread(0));
|
CHECK (2 == stat.cntThread(0));
|
||||||
CHECK (2 == stat.cntThread(1));
|
CHECK (2 == stat.cntThread(1));
|
||||||
CHECK (0 == stat.cntThread(3));
|
CHECK (0 == stat.cntThread(3));
|
||||||
CHECK (isLimited(0, stat.avgConcurrency, 2));
|
CHECK (isLimited(1, stat.avgConcurrency, 2));
|
||||||
CHECK (stat.timeAtConc(0) == 0.0);
|
CHECK (0 == stat.timeAtConc(0));
|
||||||
|
CHECK (0 < stat.timeAtConc(1));
|
||||||
|
CHECK (0 < stat.timeAtConc(2));
|
||||||
|
CHECK (0 == stat.timeAtConc(3));
|
||||||
CHECK (stat.timeAtConc(1) < stat.coveredTime);
|
CHECK (stat.timeAtConc(1) < stat.coveredTime);
|
||||||
CHECK (stat.timeAtConc(2) < stat.coveredTime);
|
CHECK (stat.timeAtConc(2) < stat.coveredTime);
|
||||||
|
|
||||||
auto isNumEq = [](double d1, double d2){ return 0,001 > abs(d1-d2); };
|
|
||||||
|
|
||||||
CHECK (isNumEq (stat.avgConcurrency, (1*stat.timeAtConc(1) + 2*stat.timeAtConc(2)) // average concurrency is a weighted mean
|
CHECK (isNumEq (stat.avgConcurrency, (1*stat.timeAtConc(1) + 2*stat.timeAtConc(2)) // average concurrency is a weighted mean
|
||||||
/ stat.coveredTime)); // of the times spent at each concurrency level
|
/ stat.coveredTime)); // of the times spent at each concurrency level
|
||||||
|
|
||||||
CHECK (isNumEq (stat.cumulatedTime , stat.timeThread(0) + stat.timeThread(1))); // cumulated time is spent in both threads
|
CHECK (isNumEq (stat.cumulatedTime , stat.timeCase(0) + stat.timeCase(2))); // cumulated time compounds all cases, including overlap
|
||||||
CHECK (isNumEq (stat.cumulatedTime , stat.timeCase(0) + stat.timeCase(2))); // and likewise in all cases together
|
CHECK (isNumEq (stat.activeTime , stat.timeThread(0) + stat.timeThread(1))); // while active time disregards overlapping activities per thread
|
||||||
CHECK (isNumEq (stat.coveredTime , stat.timeAtConc(1) + stat.timeAtConc(2))); // the covered time happens at any non-zero concurrency level
|
CHECK (isNumEq (stat.coveredTime , stat.timeAtConc(1) + stat.timeAtConc(2))); // the covered time happens at any non-zero concurrency level
|
||||||
|
|
||||||
CHECK (stat.timeCase(2) < stat.timeCase(0)); // Note: case-2 is nested into case-0
|
CHECK (stat.timeCase(2) < stat.timeCase(0)); // Note: case-2 is nested into case-0
|
||||||
|
|
@ -253,7 +265,23 @@ SHOW_EXPR(stat.timeAtConc(2));
|
||||||
void
|
void
|
||||||
perform_multithreadStressTest()
|
perform_multithreadStressTest()
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED("verify thread-safe operation under pressure");
|
MARK_TEST_FUN
|
||||||
|
const size_t CONCURR = std::thread::hardware_concurrency();
|
||||||
|
|
||||||
|
IncidenceCount watch;
|
||||||
|
watch.expectThreads(CONCURR)
|
||||||
|
.expectIncidents(5000);
|
||||||
|
|
||||||
|
auto act = [&]{ // two nested activities with random delay
|
||||||
|
uint delay = 100 + rand() % 800;
|
||||||
|
watch.markEnter();
|
||||||
|
sleep_for (microseconds(delay));
|
||||||
|
watch.markEnter(2);
|
||||||
|
sleep_for (microseconds(delay));
|
||||||
|
watch.markLeave(2);
|
||||||
|
watch.markLeave();
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111085,6 +111085,18 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
||||||
<icon BUILTIN="button_ok"/>
|
<icon BUILTIN="button_ok"/>
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
|
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1707936281530" ID="ID_710775644" MODIFIED="1707936315141" TEXT="Inkonsistenz in der Auswertung : beobachtete Cases können sich überlappen">
|
||||||
|
<icon BUILTIN="messagebox_warning"/>
|
||||||
|
<node CREATED="1707936316947" ID="ID_1876995714" MODIFIED="1707936417871" TEXT="hängt eben davon ab, wie die Start/Stop-Aufrufe erfolgen">
|
||||||
|
<icon BUILTIN="idea"/>
|
||||||
|
</node>
|
||||||
|
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#690f14" CREATED="1707936336633" ID="ID_1544753205" MODIFIED="1707936412846" TEXT="man kann ein-und-dieselbe Thread-Aktivität so durchaus mehrfach zählen">
|
||||||
|
<icon BUILTIN="clanbomber"/>
|
||||||
|
</node>
|
||||||
|
<node COLOR="#338800" CREATED="1707936355570" ID="ID_280065222" MODIFIED="1707936399484" TEXT="aber die »active time« sollte nur tatsächliche Thread-Aktivität messen">
|
||||||
|
<icon BUILTIN="button_ok"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1707754958979" ID="ID_284055307" MODIFIED="1707880406370" TEXT="IncidenceCount_test">
|
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1707754958979" ID="ID_284055307" MODIFIED="1707880406370" TEXT="IncidenceCount_test">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue