Skip to content

Commit

Permalink
merge 8.7 (timerate stability fix: no hang by iterations with quadrat…
Browse files Browse the repository at this point in the history
…ic complexity, better threshold calculation agorithm, predicting duration of next iterations)
  • Loading branch information
sebres committed Jan 23, 2025
2 parents bb88622 + 4f15fed commit 07c59c3
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 39 deletions.
126 changes: 87 additions & 39 deletions generic/tclCmdMZ.c
Original file line number Diff line number Diff line change
Expand Up @@ -4134,18 +4134,30 @@ Tcl_TimeRateObjCmd(
int result, i;
Tcl_Obj *calibrate = NULL, *direct = NULL;
Tcl_WideUInt count = 0; /* Holds repetition count */
Tcl_WideUInt lastCount = 0; /* Repetition count since last calculation. */
Tcl_WideInt maxms = WIDE_MIN;
/* Maximal running time (in milliseconds) */
Tcl_WideUInt maxcnt = WIDE_MAX;
Tcl_WideUInt maxcnt = UWIDE_MAX;
/* Maximal count of iterations. */
Tcl_WideUInt threshold = 1; /* Current threshold for check time (faster
* repeat count without time check) */
Tcl_WideUInt maxIterTm = 1; /* Max time of some iteration as max
* threshold, additionally avoiding divide to
* zero (i.e., never < 1) */
unsigned short factor = 50; /* Factor (4..50) limiting threshold to avoid
Tcl_WideUInt avgIterTm = 1; /* Average time of all processed iterations. */
Tcl_WideUInt lastIterTm = 1;/* Average time of last block of iterations. */
double estIterTm = 1.0; /* Estimated time of next iteration,
* considering the growth of lastIterTm. */
#ifdef TCL_WIDE_CLICKS
# define TR_SCALE 10 /* Fraction is 10ns (from wide click 100ns). */
#else
# define TR_SCALE 100 /* Fraction is 10ns (from 1us = 1000ns). */
#endif
#define TR_MIN_FACTOR 2 /* Min allowed factor calculating threshold. */
#define TR_MAX_FACTOR 50 /* Max allowed factor calculating threshold. */
#define TR_FACT_SINGLE_ITER 25 /* This or larger factor value will force the
* threshold 1, to avoid drastic growth of
* execution time by quadratic O() complexity. */
unsigned short factor = 16; /* Factor (2..50) limiting threshold to avoid
* growth of execution time. */
Tcl_WideInt start, middle, stop;
Tcl_WideInt start, last, middle, stop;
#ifndef TCL_WIDE_CLICKS
Tcl_Time now;
#endif /* !TCL_WIDE_CLICKS */
Expand Down Expand Up @@ -4351,7 +4363,7 @@ Tcl_TimeRateObjCmd(
*/

#ifdef TCL_WIDE_CLICKS
start = middle = TclpGetWideClicks();
start = last = middle = TclpGetWideClicks();

/*
* Time to stop execution (in wide clicks).
Expand All @@ -4363,7 +4375,7 @@ Tcl_TimeRateObjCmd(
start = now.sec;
start *= 1000000;
start += now.usec;
middle = start;
last = middle = start;

/*
* Time to stop execution (in microsecs).
Expand Down Expand Up @@ -4444,61 +4456,93 @@ Tcl_TimeRateObjCmd(
}

/*
* Don't calculate threshold by few iterations, because sometimes
* first iteration(s) can be too fast or slow (cached, delayed
* clean up, etc).
* Average iteration time (scaled) in fractions of wide clicks
* or microseconds.
*/

if (count < 10) {
threshold = 1;
continue;
}

/*
* Average iteration time in microsecs.
*/

threshold = (middle - start) / count;
if (threshold > maxIterTm) {
maxIterTm = threshold;
threshold = (Tcl_WideUInt)(middle - start) * TR_SCALE / count;
if (threshold > avgIterTm) {

/*
* Iterations seem to be longer.
*/

if (threshold > maxIterTm * 2) {
if (threshold > avgIterTm * 2) {
factor *= 2;
if (factor > 50) {
factor = 50;
}
} else {
if (factor < 50) {
factor++;
}
factor++;
}
} else if (factor > 4) {
if (factor > TR_MAX_FACTOR) {
factor = TR_MAX_FACTOR;
}
} else if (factor > TR_MIN_FACTOR) {
/*
* Iterations seem to be shorter.
*/

if (threshold < (maxIterTm / 2)) {
if (threshold < (avgIterTm / 2)) {
factor /= 2;
if (factor < 4) {
factor = 4;
if (factor < TR_MIN_FACTOR) {
factor = TR_MIN_FACTOR;
}
} else {
factor--;
}
}

if (!threshold) {
/* too short and too few iterations */
threshold = 1;
continue;
}
avgIterTm = threshold;

/*
* As relation between remaining time and time since last check,
* maximal some % of time (by factor), so avoid growing of the
* execution time if iterations are not consistent, e.g. was
* continuously on time).
* Estimate last iteration time growth and time of next iteration.
*/
lastCount = count - lastCount;
if (last != start && lastCount) {
Tcl_WideUInt lastTm;

lastTm = (Tcl_WideUInt)(middle - last) * TR_SCALE / lastCount;
estIterTm = (double)lastTm / (lastIterTm ? lastIterTm : avgIterTm);
lastIterTm = lastTm > avgIterTm ? lastTm : avgIterTm;
} else {
lastIterTm = avgIterTm;
}
estIterTm *= lastIterTm;
last = middle; lastCount = count;

threshold = ((stop - middle) / maxIterTm) / factor + 1;
/*
* Calculate next threshold to check.
* Firstly check iteration time is not larger than remaining time,
* considering last known iteration growth factor.
*/
threshold = (Tcl_WideUInt)(stop - middle) * TR_SCALE;
/*
* Estimated count of iteration til the end of execution.
* Thereby 2.5% longer execution time would be OK.
*/
if (threshold / estIterTm < 0.975) {
/* estimated time for next iteration is too large */
break;
}
threshold /= estIterTm;
/*
* Don't use threshold by few iterations, because sometimes
* first iteration(s) can be too fast or slow (cached, delayed
* clean up, etc). Also avoid unexpected execution time growth,
* so if iterations continuously grow, stay by single iteration.
*/
if (count < 10 || factor >= TR_FACT_SINGLE_ITER) {
threshold = 1;
continue;
}
/*
* Reduce it by last known factor, to avoid unexpected execution
* time growth if iterations are not consistent (may be longer).
*/
threshold = threshold / factor + 1;
if (threshold > 100000) { /* fix for too large threshold */
threshold = 100000;
}
Expand Down Expand Up @@ -4648,6 +4692,10 @@ Tcl_TimeRateObjCmd(
TclReleaseByteCode(codePtr);
}
return result;
#undef TR_SCALE
#undef TR_MIN_FACTOR
#undef TR_MAX_FACTOR
#undef TR_FACT_SINGLE_ITER
}

/*
Expand Down
20 changes: 20 additions & 0 deletions tests/cmdMZ.test
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,26 @@ test cmdMZ-6.12 {Tcl_TimeRateObjCmd: done optimization: nested call of self insi
}
list [lindex [timerate $m1 1000 5] 2] $x
} {5 20}
test cmdMZ-6.13 {Tcl_TimeRateObjCmd: stability by O(n**2), avoid long execution time on growing iteration time} {
set result {}
# test the function with quadratic complexity (iteration growth 2x, 10x, 100x):
foreach e {2 10 100} {
set x 1
set m1 [timerate {
apply {x {
while {[incr x -1]} {}
}} [set x [expr {$x*$e}]]
} 50]
lappend result "${e}x"
# check it was too slow (it is OK to use factor 2 to prevent sporadic
# errors on some slow systems or time issues, because if it is not fixed,
# the execution time may grow hundreds and thousand times):
if {[lindex $m1 6] > 50 * 2} {
lappend result "unexpected long: $m1"
}
}
set result
} {2x 10x 100x}

test cmdMZ-try-1.0 {

Expand Down

0 comments on commit 07c59c3

Please sign in to comment.