4. Clocks

Shrapnel needs to keep track of time to manage scheduling of sleeps and timeouts. Because Shrapnel is intended to support thousands of coroutines, and each coroutine may be making many timeout calls per second, Shrapnel needs to use a timing facility that is relatively high performance. It also needs one that is monotonic, so it does not need to deal with system clock changes.

The clocks subpackage is intended to provide a variety of different time facilities. Currently it only supports using the x86 TSC timer. This is a timer built in to the CPU, and thus is very fast.

4.1. TSC Time

Support for TSC time is implemented in the coro.clocks.tsc_time module.

TSC time library.

4.1.1. Introduction

This module implements a “Time” object that is based on the TSC value of the x86 processor. This is a monotonically increasing value that is somewhat dependable (whereas system time may change). It is also very high resolution and very efficient to retrieve.

This library is designed to be as high-performance as possible. If you use it correctly, you can ensure your code maintains that high level of performance.

4.1.2. Objects

There are 4 objects in this module all deriving from the base Time object. They are:

  • TSC: TSC value
  • Posix: POSIX, seconds since 1970
  • uPosix: POSIX, microseconds since 1970
  • fPosix: POSIX, floating-point seconds since 1970

Each of these objects are relatively the same. The main difference is when adding or comparing the objects, they behave as their type describes.

The base Time object defines methods for converting the TSC value to another type. They are as_posix_sec, as_posix_usec, and as_posix_fsec.

There are a three classes of functions in the module for creating these objects. The now_* functions compute the current time. The mktime_* functions convert a Python “time tuple” to a Time object. The *_from_* functions take a raw value (TSC, Posix, etc.) and create a Time object.

4.1.3. Raw Conversions

The module also provides methods for converting raw values from one type to another. These are the *_to_* functions, and generally should not be needed if your code uses Time objects throughout.

4.1.4. Wall Clock Synchronization

When the module is first imported, it captures the current wall-clock time and TSC value. This information is used for doing conversions between TSC and Posix time. It is important to call the update_time_relation function whenever the wall-clock is changed. Also, it is a good idea to call it periodically to retain accuracy. This is necessary because the library uses the ticks_per_sec value for conversions. This value is obtained from the machdep.tsc_freq sysctl, and may be slightly off (my system is about 0.002% off which is about 10 minutes per year). Long-term computations based on the ticks_per_sec value should not be trusted to be very accurate.

4.1.5. Accuracy and Precision

The conversion functions use roughly imprecise, but faster integer arithmetic. The reason it is inaccurate is because it uses the ticks_per_usec value which is less accurate than ticks_per_sec. To compute the inaccuracy, you can use the formula:

(1000000 / ticks_per_sec)

This is a rough estimate. On my 3.5 GHz system, this is about 0.027% or about 2.3 hours per year. Slower systems have less accuracy (about 0.09% for a 1 GHz machine or about 8 hours per year).

To be more accurate, we would either need to use numbers larger than 64 bits (bignums, Python Longs, 80-bit C doubles, etc.), but it would slow the conversions down a little (for C doubles it was about 30% slower on my system).

TSC values that are significantly far from the current time should not be trusted to be very accurate.

4.1.6. External C Access

The C functions in this module are available for direct access from other C extension modules. For C modules, simply include “tsc_time.h” and call the initialization function once. For Pyrex modules, include “tsc_time_include.pyx”. See the respective files for more detail.

4.1.7. Signedness

TSC values may be negative (to indicate a time before the computer booted). In general, this library uses signed data types to avoid signed/unsigned multiplication/division. A particular exception is the ticks_per_sec value because it is currently defined as a 32-bit number, and we need to support machines with CPU’s faster than 2GHz.

On most POSIX systems, time_t is a signed 32-bit integer (on some it is a signed 64-bit integer, whose negative value extends past the beginning of the universe). In theory, a signed 32-bit value can handle negative values from 1901 to 1970 and 1970 to 2038 positive. Some foolish systems have attempted to define time_t as an unsigned value to extend the overflow point to 2106, but this is rare.

4.1.8. Notes

The rate of the TSC value may change on systems with thermal and power throttling. (though rumor has it some processors adjust the TSC rate when auto-throttling to ensure it runs at a constant speed). This invalidates assumptions made in this library, so do not use those features.

On SMP kernels, FreeBSD will synchronize the TSC value on all CPU’s at boot time, and the assumption is made that they will remain roughly in sync. Rumor has it that some motherboards will attempt to keep the TSC value in sync on all processors over time. AMD CPU’s are rumored to be especially vulnerable to this.

4.1.9. RDTSC

This is detailed low-level information about the rdtsc instruction that is used to obtain the TSC value.

rdtsc - ReaD TimeStamp Counter

The cycle counter in the Pentium series of processors is incremented once for every clock cycle. It starts out as 0 on system boot. It is a 64-bit number, and thus will wrap over in 292 years on a 2 gigahertz processor. It should keep counting unless the system goes into deep sleep mode.

FYI, the control registers on the Pentium can be configured to restrict RDTSC to privileged level 0.

The RDTSC instruction is generally not synchronized. Thus, with out of order execution, it is possible for it to run ahead of other instructions that came before it. This is mainly only important if you are trying to do exact profiling of instruction cycles.

4.1.10. Other Counters

Most x86 systems have other hardware timers. They all have different frequencies, accuracies, performance characteristics, etc. The following is a list of alternate counters that we may want to investigate:

  • Intel 8254 Interval Timer (i8254). This was introduced in the IBM AT (the 8253 was used in the IBM XT).
  • ACPI (Advanced Configuration and Power Interface) counter (ACPI was introduced around 1996).
  • HPET (High Precision Event Timer) introduced by Intel around 2004 as a replacement to the i8254.

4.1.11. Further Reading

Some interesting papers:

4.1.12. TODO

  • Investigate SMP drift over long periods of time.
  • Find a way to detect if the current platform has thermal or power throttling, and whether or not it compensates the TSC rate to remain constant.
  • machdep.tsc_freq is a 32-bit unsigned integer. For systems with CPU’s faster that 4 GHz, this is no longer sufficient.
  • Get a better (more accurate) value of machdep.tsc_freq. Investigate CLK_USE_TSC_CALIBRATION, CLK_USE_I8254_CALIBRATION, CLK_CALIBRATION_LOOP in FreeBSD kernel which use the mc146818A chip. (CLK_USE_TSC_CALIBRATION seems to have disappeared, but is available in older kernels.)
  • Write something that will periodically adjust the ticks_per_sec value to be more accurate, comparing against the wall clock assuming the wall clock is adjusted with NTP. See djb’s clockspeed for inspiration.
Variables:
  • ticks_per_sec: Number of processor ticks per second.
  • ticks_per_usec: Number of processor ticks per microsecond.
  • relative_usec_time: Value of POSIX time (in microseconds) that relates to relative_tsc_time.
  • relative_tsc_time: Value of TSC counter that corresponds to relative_usec_time.
  • relative_sec_time: Value of POSIX time (in seconds) that relates to relative_tsc_time.
class coro.clocks.tsc_time.Posix

Bases: coro.clocks.tsc_time.Time

Time in POSIX seconds.

coro.clocks.tsc_time.Posix_from_posix_fsec()

Convert a raw POSIX floating-point seconds value to a Posix object.

coro.clocks.tsc_time.Posix_from_posix_sec()

Convert a raw POSIX seconds value to a Posix object.

coro.clocks.tsc_time.Posix_from_posix_usec()

Convert a raw POSIX microseconds value to a Posix object.

coro.clocks.tsc_time.Posix_from_ticks()

Convert a raw TSC value to a Posix object.

class coro.clocks.tsc_time.TSC

Bases: coro.clocks.tsc_time.Time

Time in TSC ticks.

coro.clocks.tsc_time.TSC_from_posix_fsec()

Convert a raw POSIX floating-point seconds value to a TSC object.

coro.clocks.tsc_time.TSC_from_posix_sec()

Convert a raw POSIX seconds value to a TSC object.

coro.clocks.tsc_time.TSC_from_posix_usec()

Convert a raw POSIX microseconds value to a TSC object.

coro.clocks.tsc_time.TSC_from_ticks()

Convert a raw TSC value to a TSC object.

class coro.clocks.tsc_time.Time

Bases: object

Base time object.

Time object support the following operations:

  • Comparison. Comparison is done using the native time object type. For example, “Posix” objects compare time in POSIX seconds. Thus, if comparing two Posix objects that have slightly different TSC values, but due to the loss of precision have the same POSIX value, they will compare as equal.

    Comparison between different types is OK (Posix compared to TSC). You can also compare a value with Python numeric literals (int, long, float, etc.).

  • Hashing. Hashing is based on the object type.

  • Addition and subtraction. This only works between two types of the exact same type (Posix and Posix for example), or with a Python numeric literal (int, long, float, etc.).

  • int/long/float. Calling the int, long, or float functions on the object will return an int, long, or float value of the object’s type.

Ivariables :
  • tsc: The time in TSC.
as_posix_fsec()

Return the time as POSIX seconds (a floating-point number).

Return :Returns a float as POSIX seconds.
as_posix_sec()

Return the time as POSIX seconds (an integer).

Return :Returns an integer as POSIX seconds.
as_posix_usec()

Return the time as POSIX microseconds (a long).

Return :Returns a long as POSIX microseconds.
ctime()

Return the time as a string.

This returns the time as a classic C-style 24-character time string in the local timezone in the format ‘Sun Jun 20 23:21:05 1993’. This does not include a trailing newline like C does.

Return :Returns a string of the local time.
gmtime()

Return a Python time-tuple in UTC.

Return :Returns a time.struct_time time-tuple in UTC.
localtime()

Return a Python time-tuple in the local timezone.

Return :Returns a time.struct_time time-tuple in the local timezone.
mkstr_local()

Convert time to a string in the local timezone.

Parameters :
  • format: The format that you want the string as. See the strftime function in the time module for more details.
Return :

Returns a string in the local timezone.

mkstr_utc()

Convert time to a string in UTC.

Parameters :
  • format: The format that you want the string as. See the strftime function in the time module for more details.
Return :

Returns a string in UTC.

class coro.clocks.tsc_time.fPosix

Bases: coro.clocks.tsc_time.Time

Time in POSIX seconds as a floating-point number.

coro.clocks.tsc_time.fPosix_from_posix_fsec()

Convert a raw POSIX floating-point seconds value to an fPosix object.

coro.clocks.tsc_time.fPosix_from_posix_sec()

Convert a raw POSIX seconds value to an fPosix object.

coro.clocks.tsc_time.fPosix_from_posix_usec()

Convert a raw POSIX microseconds value to an fPosix object.

coro.clocks.tsc_time.fPosix_from_ticks()

Convert a raw TSC value to an fPosix object.

coro.clocks.tsc_time.fsec_to_ticks()

Convert POSIX seconds (a floating-point number) to ticks.

Parameters :
  • t: The time in POSIX seconds (a float).
Return :

Returns the time in TSC ticks.

coro.clocks.tsc_time.get_kernel_usec()

Get the current time from the kernel in microseconds.

Avoid using this unless absolutely necessary due to performance reasons.

Return :Returns the current time in microseconds.
coro.clocks.tsc_time.mktime_posix_fsec()

Convert a Python time-tuple to an fPosix object.

coro.clocks.tsc_time.mktime_posix_sec()

Convert a Python time-tuple to a Posix object.

coro.clocks.tsc_time.mktime_posix_usec()

Convert a Python time-tuple to a uPosix object.

coro.clocks.tsc_time.mktime_tsc()

Convert a Python time-tuple to a TSC object.

coro.clocks.tsc_time.now_posix_fsec()

Return an fPosix object of the current time.

coro.clocks.tsc_time.now_posix_sec()

Return a Posix object of the current time.

coro.clocks.tsc_time.now_posix_usec()

Return a uPosix object of the current time.

coro.clocks.tsc_time.now_raw_posix_fsec()

Get the current time as raw POSIX floating-point seconds.

coro.clocks.tsc_time.now_raw_posix_sec()

Get the current time as raw POSIX seconds.

coro.clocks.tsc_time.now_raw_posix_usec()

Get the current time as raw POSIX microseconds.

coro.clocks.tsc_time.now_raw_tsc()

Get the current time as raw ticks.

coro.clocks.tsc_time.now_tsc()

Return a TSC object of the current time.

coro.clocks.tsc_time.rdtsc()

Return the current TSC value.

coro.clocks.tsc_time.sec_to_ticks()

Convert POSIX seconds to ticks.

Parameters :
  • t: The time in POSIX seconds (an integer).
Return :

Returns the time in TSC ticks.

coro.clocks.tsc_time.set_time()

Emulate setting the system time to the given timestamp.

This alters the library to behave as-if the current time is the given time. Note that this is different than changing the clock on the system. Changing the clock on the system does not affect TSC values, but this function does affect TSC values to behave as-if time has elapsed in the real world.

Parameters :
  • posix_timestamp: The POSIX timestamp (in seconds) to set the current time. Pass in a value of 0 to disable emulation.
coro.clocks.tsc_time.step_time()

Emulate changing the system time by the given number of seconds.

See set_time for more detail.

Parameters :
  • delta_secs: The number of seconds to alter the current time.
coro.clocks.tsc_time.ticks_to_fsec()

Convert ticks to POSIX seconds (a floating-point number).

Parameters :
  • t: The time in TSC.
Return :

Returns the time in POSIX microseconds.

coro.clocks.tsc_time.ticks_to_sec()

Convert ticks to POSIX seconds (an integer).

Parameters :
  • t: The time in TSC.
Return :

Returns the time in POSIX microseconds.

coro.clocks.tsc_time.ticks_to_usec()

Convert ticks to POSIX microseconds.

Parameters :
  • t: The time in TSC.
Return :

Returns the time in POSIX microseconds.

coro.clocks.tsc_time.ticks_to_usec_safe()

Convert ticks to POSIX microseconds.

This is “safe” in that if the value is zero, then it returns zero.

Parameters :
  • t: The time in TSC.
Return :

Returns the time in POSIX microseconds.

class coro.clocks.tsc_time.uPosix

Bases: coro.clocks.tsc_time.Time

Time in POSIX microseconds.

coro.clocks.tsc_time.uPosix_from_posix_fsec()

Convert a raw POSIX floating-point seconds value to a uPosix object.

coro.clocks.tsc_time.uPosix_from_posix_sec()

Convert a raw POSIX seconds value to a uPosix object.

coro.clocks.tsc_time.uPosix_from_posix_usec()

Convert a raw POSIX microseconds value to a uPosix object.

coro.clocks.tsc_time.uPosix_from_ticks()

Convert a raw TSC value to a uPosix object.

coro.clocks.tsc_time.update_time_relation()

Update the relative time stamps.

You should call this whenever you think the clock has been changed. It should also be called periodically due to inaccuracies in the ticks_per_sec value.

coro.clocks.tsc_time.usec_to_ticks()

Convert POSIX microseconds to ticks.

Parameters :
  • t: The time in POSIX microseconds.
Return :

Returns the time in TSC ticks.

coro.clocks.tsc_time.usec_to_ticks_safe()

Convert POSIX microseconds to ticks.

This is “safe” in that if the value is zero, then it returns zero.

Parameters :
  • t: The time in POSIX microseconds.
Return :

Returns the time in TSC ticks.