Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 5 Mar 2013 21:43:30 +1100 (EST)
From:      Bruce Evans <brde@optusnet.com.au>
To:        Alexander Motin <mav@FreeBSD.org>
Cc:        Davide Italiano <davide@FreeBSD.org>, arch@FreeBSD.org
Subject:   Re: tickless design guidelines
Message-ID:  <20130305204500.T1059@besplex.bde.org>
In-Reply-To: <5135B934.5060202@FreeBSD.org>
References:  <20130305080134.GC13187@onelab2.iet.unipi.it> <5135AFAD.70408@FreeBSD.org> <20130305090735.GB18221@onelab2.iet.unipi.it> <5135B934.5060202@FreeBSD.org>

next in thread | previous in thread | raw e-mail | index | archive | help
On Tue, 5 Mar 2013, Alexander Motin wrote:

> On 05.03.2013 11:07, Luigi Rizzo wrote:
>> Also i wonder if it may make sense to add a feature so that whenever
>> we get an interrupt and a fast and suitable timecounter is available,
>> some system-wide bintime is updated.

bintime is intentionally updated as little as possible, since updating
it is an expensive operation that would need even more expensive locking
if it is done often.  Note that get*time() is fundamentally broken,
since the timestamps that it returns are incoherent with timestamps
returned by non-get*time().  I used to sync get*time() with non-get*time()
on every call to the latter.  E.g., on every call to bintime(),
time_second, timehands->th_microtime and timehands->th_nanotime are
updated with the time just read by binuptime().  getbintime(), etc.,
use the possibly-updated values.  This makes getbintime(), etc., more
accurate if calls to them are mixed with calls to bintime(), but this
is a side effect.  The sync is done for correctness.  It ensures that
the time is monotonic when read using any mixture of get/non-get*time()
APIs.  File timestamps written using the value of time_second or
getnanotime() are noticebly broken when compared with the real time
read using gettimeofday() without this.Z.  But this is slow, and phk
didn't like it.  I only did it for the !SMP cases and dropped it after
a few years.

Your suggestion is similar.  Updating the system-wide bintime requires
knowing the current bintime, and nothing much more or less than a very
recent read of a hardware timecounter gives that.  The difficulty is
atomically updating the whole timecounter state with values derived
from that.

>> So getbinuptime() could just use this counter, becoming extremely
>> cheap (perhaps it is already, i am not sure) and in the long term,
>> as CPUs with fixed frequency TSC become ubiquitous,
>> we would get higher resolution as the interrupt load increases.

getbinuptime() is already extremely cheap, but imprecise and incoherent.

If you assume a fixed frequency TSC, then you can just always use
bintime().  Unfortunately, the same CPUs that have a fixed frequency
TSC also have slow-to-read TSCs.  The TSC read time increased from ~9
cycles on AthlonXP and Athlon64 to 42 on Phenom, and it core2 is even
slower.  9 cycles were almost in the noise, so getbintime() was only
slightly faster than bintime() on Athlon64.

> Each time timer interrupt fires, or CPU timers "switched" between idle
> and active modes present time is fetched. The only question is how to
> store it somewhere safely. It was difficult for 96/128-bit struct
> bintime. It is easier for 64-bit sbintime_t, but still not all archs are
> able to do it atomically. The mechanism used for getbinuptime() now may
> not scale well to the very high update rates.

getbinuptime() with high update rates would reduce to a bad version of
binuptime().  Actually, getbinuptime() would probably be unchanged.  The
updates would be in binuptime().  You could update only every 10 or 100
usec to get not so high update rates but not coherency.

Actually^2, timekeeping is unimportant for timeouts.  Thus you could use
sloppy non-atomic updates for everything and barely notice losing the
races for this.  To get semi-coherent and more accurate (when the races
aren't lost) in sbinuptime(), just record a delta-time on every hardware
timecounter call.  Arrange to call hardware timecounters fairly often,
and add the delta time in sbinuptime().  Better not touch getbinuptime(),
etc.  Let them remain mostly race-free and incoherent.

Here is my old syncing code:

@ diff -c2 src/sys/kern/kern_tc.c~ src/sys/kern/kern_tc.c
@ *** src/sys/kern/kern_tc.c~	Fri Mar  5 17:03:07 2004
@ --- src/sys/kern/kern_tc.c	Fri Mar  5 17:03:08 2004
@ ***************
@ *** 172,175 ****
@ --- 224,254 ----
@   }
@ 
@ + #define SYNC_TIME
@ + 
@ + #if defined(SYNC_TIME) && !defined(SMP)
@ + static void
@ + sync_time(long sec, long nsec, long usec)

nanotime() and microtime() call here with the time that they just read.
I don't bother syncing full bintimes, since this was mainly intended to
fix file timestamps.

@ + {
@ + 	register_t saveintr;
@ + 
@ + 	saveintr = intr_disable();

Locking for !SMP only.

@ + 	if (time_second < sec)
@ + 		time_second = sec;
@ + 	if (timehands->th_microtime.tv_sec < sec ||
@ + 	    (timehands->th_microtime.tv_sec == sec &&
@ + 	    timehands->th_microtime.tv_usec < usec)) {
@ + 		timehands->th_microtime.tv_sec = sec;
@ + 		timehands->th_microtime.tv_usec = usec;
@ + 	}
@ + 	if (timehands->th_nanotime.tv_sec < sec ||
@ + 	    (timehands->th_nanotime.tv_sec == sec &&
@ + 	    timehands->th_nanotime.tv_nsec < nsec)) {
@ + 		timehands->th_nanotime.tv_sec = sec;
@ + 		timehands->th_nanotime.tv_nsec = nsec;
@ + 	}

The secondary values are now coherent for use in getnanotime(), etc.

sbintime() support could set the final value here instead of a delta.
The reason to use a delta is that it fits in 32 bits so is easy to
access atomically, so that the write here doesn't need much locking.
Oops, the locking here is inadequate even for !SMP -- the updates
here may be non-monotonic since the lock is not around the whole
timcounter-read...update sequence.

@ + 	intr_restore(saveintr);
@ + }
@ + #endif /* SYNC_TIME && !SMP */
@ + 
@   void
@   bintime(struct bintime *bt)
@ ***************
@ *** 189,192 ****
@ --- 268,274 ----
@   	bintime(&bt);
@   	bintime2timespec(&bt, tsp);
@ + #if defined(SYNC_TIME) && !defined(SMP)
@ + 	sync_time(tsp->tv_sec, tsp->tv_nsec, tsp->tv_nsec / 1000);
@ + #endif
@   }
@

Any call to bintime() syncs time_second, and the timespec and timeval in
the timehands, but not the full bintime.  So for example, getnanotime()
benefits from bintime() being called, but getbintime() remains incoherent
with bintime().

Bruce



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20130305204500.T1059>