timeout lib: dynamic interpolation-factor shift

In the timeout framework, we maintain a translation factor value to
translate between time and timestamps. To raise precision we scale-up
the factor when we calculate it and scale-down the result of its
appliance later again. This up and down scaling is achieved through
left and right shifting. Until now, the shift width was statically
choosen. However, some platforms need a big shift width and others a
smaller one. The one static shift width couldn't cover all platforms
which caused overflows or precision problems.

Now, the shift width is choosen optimally for the actual translation
factor each time it gets re-calculated. This way, we can take care that
the shift always renders the best precision level without the risk for
overflows.

Ref #2400
This commit is contained in:
Martin Stein 2017-06-19 12:56:16 +02:00 committed by Norman Feske
parent 99d971f348
commit 9b1c26ab7f
3 changed files with 138 additions and 51 deletions

View File

@ -187,16 +187,8 @@ class Timer::Connection : public Genode::Connection<Session>,
** Time_source helpers **
*************************/
/*
* The higher the factor shift, the more precise is the time
* interpolation but the more likely it becomes that an overflow
* would occur during calculations. In this case, the timer
* down-scales the values live which is avoidable overhead.
*/
enum { TS_TO_US_RATIO_SHIFT = 4 };
enum { MIN_TIMEOUT_US = 5000 };
enum { REAL_TIME_UPDATE_PERIOD_US = 500000 };
enum { MAX_TS = ~(Timestamp)0ULL >> TS_TO_US_RATIO_SHIFT };
enum { MAX_INTERPOLATION_QUALITY = 3 };
enum { MAX_REMOTE_TIME_LATENCY_US = 500 };
enum { MAX_REMOTE_TIME_TRIALS = 5 };
@ -210,14 +202,17 @@ class Timer::Connection : public Genode::Connection<Session>,
Duration _real_time { Milliseconds(_ms) };
Duration _interpolated_time { _real_time };
unsigned _interpolation_quality { 0 };
unsigned long _us_to_ts_factor { 1UL << TS_TO_US_RATIO_SHIFT };
unsigned long _us_to_ts_factor { 1UL };
unsigned _us_to_ts_factor_shift { 0 };
Timestamp _timestamp();
void _update_interpolation_quality(unsigned long min_factor,
unsigned long max_factor);
unsigned long _ts_to_us_ratio(Timestamp ts, unsigned long us);
unsigned long _ts_to_us_ratio(Timestamp ts,
unsigned long us,
unsigned shift);
void _update_real_time();

View File

@ -25,8 +25,6 @@ void Timer::Connection::_update_interpolation_quality(unsigned long min_factor,
/*
* If the factor gets adapted less than 12.5%, we raise the
* interpolation-quality value. Otherwise, we reset it to zero.
* We can safely do the shift on the factor as it should be
* at least of value 1 << TS_TO_US_RATIO_SHIFT.
*/
if ((max_factor - min_factor) < (max_factor >> 3)) {
if (_interpolation_quality < MAX_INTERPOLATION_QUALITY) {
@ -38,7 +36,8 @@ void Timer::Connection::_update_interpolation_quality(unsigned long min_factor,
unsigned long Timer::Connection::_ts_to_us_ratio(Timestamp ts,
unsigned long us)
unsigned long us,
unsigned shift)
{
/*
* If the timestamp difference is to big to do the following
@ -46,7 +45,8 @@ unsigned long Timer::Connection::_ts_to_us_ratio(Timestamp ts,
* and time difference down equally. This should neither happen
* often nor have much effect on the resulting factor.
*/
while (ts > MAX_TS) {
Timestamp const max_ts = ~(Timestamp)0ULL >> shift;
while (ts > max_ts) {
warning("timestamp value too big");
ts >>= 1;
us >>= 1;
@ -59,13 +59,13 @@ unsigned long Timer::Connection::_ts_to_us_ratio(Timestamp ts,
* the calculation. This upscaling must be considered when using
* the result.
*/
Timestamp const result = (ts << TS_TO_US_RATIO_SHIFT) / us;
Timestamp const result = (ts << shift) / us;
unsigned long const result_ul = (unsigned long)result;
if (result == result_ul) {
return result_ul; }
warning("Timestamp-to-time ratio too big");
return ~0UL;
if (result != result_ul) {
warning("Timestamp-to-time ratio too big");
return ~0UL;
}
return result_ul;
}

View File

@ -23,55 +23,150 @@ void Timer::Connection::_update_real_time()
{
Lock_guard<Lock> lock_guard(_real_time_lock);
Timestamp ts = 0UL;
unsigned long ms = 0UL;
unsigned long us_diff = ~0UL;
/*
* Update timestamp, time, and real-time value
*/
Timestamp ts = 0UL;
unsigned long ms = 0UL;
unsigned long latency_us = ~0UL;
/*
* We retry reading out timestamp plus remote time until the result
* fulfills a given latency. If the maximum number of trials is
* reached, we take the results that has the lowest latency.
*/
for (unsigned remote_time_trials = 0;
remote_time_trials < MAX_REMOTE_TIME_TRIALS;
remote_time_trials++)
{
/* determine time and timestamp difference since the last call */
/* read out the two time values close in succession */
Timestamp volatile new_ts = _timestamp();
unsigned long volatile new_ms = elapsed_ms();
/*
* If interpolation is not ready, yet we cannot determine a latency
* and take the values as they are.
*/
if (_interpolation_quality < MAX_INTERPOLATION_QUALITY) {
ms = new_ms;
ts = new_ts;
break;
}
/* determine latency between reading out timestamp and time value */
Timestamp const ts_diff = _timestamp() - new_ts;
unsigned long const new_latency_us =
_ts_to_us_ratio(ts_diff, _us_to_ts_factor, _us_to_ts_factor_shift);
Timestamp const ts_diff = _timestamp() - new_ts;
unsigned long const new_us_diff = _ts_to_us_ratio(ts_diff,
_us_to_ts_factor);
/* remember results only if the latency was better than last time */
if (new_us_diff < us_diff) {
/* remember results if the latency was better than on the last trial */
if (new_latency_us < latency_us) {
ms = new_ms;
ts = new_ts;
if (us_diff < MAX_REMOTE_TIME_LATENCY_US) {
/* take the results if the latency fulfills the given maximum */
if (latency_us < MAX_REMOTE_TIME_LATENCY_US) {
break;
}
}
}
/* determine timestamp and time difference */
unsigned long const ms_diff = ms - _ms;
Timestamp const ts_diff = ts - _ts;
Timestamp ts_diff = ts - _ts;
/* update real time and values for next difference calculation */
/* overwrite timestamp, time, and real time member */
_ms = ms;
_ts = ts;
_real_time += Milliseconds(ms_diff);
unsigned long const new_factor = _ts_to_us_ratio(ts_diff, ms_diff * 1000UL);
unsigned long const old_factor = _us_to_ts_factor;
/* update interpolation-quality value */
if (old_factor > new_factor) { _update_interpolation_quality(new_factor, old_factor); }
else { _update_interpolation_quality(old_factor, new_factor); }
/*
* Update timestamp-to-time factor and its shift
*/
_us_to_ts_factor = new_factor;
enum { MAX_FACTOR_SHIFT = sizeof(unsigned long) * 8 };
unsigned factor_shift = _us_to_ts_factor_shift;
unsigned long old_factor = _us_to_ts_factor;
unsigned long const us_diff = ms_diff * 1000UL;
Timestamp max_ts_diff = ~(Timestamp)0ULL >> factor_shift;
Timestamp min_ts_diff_shifted = ~(Timestamp)0ULL >> 1;
/*
* If the calculation type is bigger than the resulting factor type,
* we have to apply further limitations to avoid a loss at the final cast.
*/
if (sizeof(Timestamp) > sizeof(unsigned long)) {
Timestamp limit_ts_diff_shifted = (Timestamp)~0UL * us_diff;
Timestamp const limit_ts_diff = limit_ts_diff_shifted >>
factor_shift;
/*
* Avoid that we leave the factor shift on such a high level that
* casting the factor to its final type causes a loss.
*/
if (max_ts_diff > limit_ts_diff) {
max_ts_diff = limit_ts_diff;
}
/*
* Avoid that we raise the factor shift such that casting the factor
* to its final type causes a loss.
*/
limit_ts_diff_shifted >>= 1;
if (min_ts_diff_shifted > limit_ts_diff_shifted) {
min_ts_diff_shifted = limit_ts_diff_shifted;
}
}
struct Factor_update_failed : Genode::Exception { };
try {
/* meet the timestamp-difference limit before applying the shift */
while (ts_diff > max_ts_diff) {
/* if possible, lower the shift to meet the limitation */
if (!factor_shift) {
error("timestamp difference too big");
throw Factor_update_failed();
}
factor_shift--;
max_ts_diff = (max_ts_diff << 1) | 1;
old_factor >>= 1;
}
/*
* Apply current shift to timestamp difference and try to even
* raise the shift successively to get as much precision as possible.
*/
Timestamp ts_diff_shifted = ts_diff << factor_shift;
while (ts_diff_shifted <= min_ts_diff_shifted &&
factor_shift < MAX_FACTOR_SHIFT)
{
factor_shift++;
ts_diff_shifted <<= 1;
old_factor <<= 1;
}
/*
* The cast to unsigned long does not cause a loss because of the
* limitations we applied to the timestamp difference.
*/
unsigned long const new_factor =
(unsigned long)((Timestamp)ts_diff_shifted / us_diff);
/* update interpolation-quality value */
if (old_factor > new_factor) { _update_interpolation_quality(new_factor, old_factor); }
else { _update_interpolation_quality(old_factor, new_factor); }
/* overwrite factor and factor-shift member */
_us_to_ts_factor_shift = factor_shift;
_us_to_ts_factor = new_factor;
} catch (Factor_update_failed) {
/* disable interpolation */
_interpolation_quality = 0;
}
}
@ -88,30 +183,27 @@ Duration Timer::Connection::curr_time()
* a yet unstable factor, there's an increased risk that the
* interpolated time falsely reaches an enourmous level. Then
* the value would stand still for quite some time because we
* can't let it jump back to a more realistic level. This
* would also eliminate updates of the real time as the
* timeout scheduler that manages our update timeout also
* uses this function.
* can't let it jump back to a more realistic level.
*/
if (_interpolation_quality == MAX_INTERPOLATION_QUALITY)
{
/* locally buffer real-time related members */
Timestamp const ts = _ts;
unsigned long const us_to_ts_factor = _us_to_ts_factor;
/* buffer interpolation related members and free the lock */
Timestamp const ts = _ts;
unsigned long const us_to_ts_factor = _us_to_ts_factor;
unsigned const us_to_ts_factor_shift = _us_to_ts_factor_shift;
lock_guard.destruct();
/* get time difference since the last real time update */
/* interpolate time difference since the last real time update */
Timestamp const ts_diff = _timestamp() - ts;
unsigned long const us_diff = _ts_to_us_ratio(ts_diff, us_to_ts_factor);
unsigned long const us_diff = _ts_to_us_ratio(ts_diff, us_to_ts_factor,
us_to_ts_factor_shift);
interpolated_time += Microseconds(us_diff);
} else {
/*
* Use remote timer instead of timestamps
*/
/* use remote timer instead of timestamps */
interpolated_time += Milliseconds(elapsed_ms() - _ms);
lock_guard.destruct();