简体   繁体   中英

extending the std::chrono functionality to deal with run-time (non compile-time) constant periods

I have been experimenting with all kind of timers on Linux and OSX, and would like to try and wrap some of them with the same interface used by std::chrono.

That's easy to do for timers that have a well-defined "period" at compile time, eg the POSIX clock_gettime() familiy, the clock_get_time() family on OSX, or gettimeofday().

However, there are some useful timers for which the "period" - while constant - is only known at runtime. For example: - POSIX states the period of clock(), CLOCKS_PER_SEC, may be a variable on non-XSI systems - on Linux, the period of times() is given at runtime by sysconf(_SC_CLK_TCK) - on OSX, the period of mach_absolute_time() is given at runtime by mach_timebase_info() - on recent Intel processors, the DST register ticks at a constant rate, but of course that can only be determined at runtime

To wrap these timers in the std::chrono interface, one possibility would be to use a period of std::chrono::nanosecond , and convert the value of each timer to nanoseconds. An other approach could be to use a floating point representation. However, both approaches would introduce a (very small) overhead to the now() function, and a (probably small) loss in precision.

The solution I'm trying to pursue is to define a set of classes to represent such "run-time constant" periods, built along the same lines as the std::ratio class. However I expect that will require rewriting all the related template classes and functions (as they assume constexpr values).

How do I wrap these kind of timers a la std:chrono ?

Or use non-constexpr values for the time period of a clock ?

Does anyone have any experience with wrapping these kind of timers a la std:chrono ?

Actually I do. And on OSX, one of your platforms of interest. :-)

You mention:

on OSX, the period of mach_absolute_time() is given at runtime by mach_timebase_info()

Absolutely correct. Also on OSX, the libc++ implementation of high_resolution_clock and steady_clock is actually based on mach_absolute_time . I'm the author of this code, which is open source with a generous license (do anything you want with it as long as you retain the copyright).

Here is the source for libc++'s steady_clock::now() . It is built pretty much the way you surmised. The run time period is converted to nanoseconds prior to returning. On OS X the conversion factor is very often 1, and the code takes advantage of that fact with an optimization. However the code is general enough to handle non-1 conversion factors.

On the first call to now() there's a small cost of querying the run time conversion factor to nanoseconds. In the general case a floating point conversion factor is computed. In the common case (conversion factor == 1) the subsequent cost is calling through a function pointer. I've found that the overhead is really quite reasonable.

On OS X the conversion factor, although not determined until run time, is still a constant (ie does not vary as the program executes), so it only needs to be computed once.

If you're in a situation where your period is actually varying dynamically, you'll need more infrastructure to handle this. Essentially you would need to integrate (calculus) the period vs time curve and then compute an average period between two points in time. That would require a constant monitoring of the period as it changes with time, and <chrono> isn't the right tool for that. Such tools are typically handled at the OS level.

[Does anyone have any experience] Or with using non-constexpr values for the time period of a clock ?

After reading through the standard (20.11.5, Class template duration), "period" is expected to be "a specialization of ratio":

Remarks: If Period is not a specialization of ratio, the program is ill-formed.

and all chrono templates rely heavily on constexpr functionality.

Does anyone have any experience with wrapping these kind of timers a la std:chrono ?

I've found here a suggestion to use a duration with period = 1, boost::rational as rep , though without any concrete examples.

I have done a similar thing for my purposes, only for Linux though. You find the code here ; feel free to use the code in whatever way you want.

The challenges my implementation addresses overlap partially with the ones mentioned in your question. Specifically:

  • The tick factor (required to convert from clock ticks to a time unit based on seconds) is retrieved at run time, but only the first time now() is used . If you are concerned about the small overhead this causes, you may call the now() function once at start-up before you measure any actual intervals. The tick factor is stored in a static variable, which means there is still some overhead as – on the lowest level – each call of the now() function implies checking whether the static variable has been initialized. However, this overhead will be the same in each call of now() , so it shouldn't impact measuring time intervals.

  • I do not convert to nanoseconds by default, because when measuring relatively long periods of time (eg a few seconds) this causes overflows very quickly. This is in fact the main reason why I don't use the boost implementation. Instead of converting to nanoseconds, I implement the base unit as a template parameter (called Precision in the code). I use std::ratio from C++11 as template arguments. So I can choose, for example, a clock<micro> , which implies that calling the now() function will internally convert to microseconds rather than nanoseconds, which means I can measure periods of many seconds or minutes without overflows and still with good precision. (This is independent of the unit used to produce output. You can have a clock<micro> and display the result in seconds, etc.)

  • My clock type, which is called combined_clock combines user time, system time and wall-clock time. There is a boost clock type for this, too, but it's not compatible with the ratio types and units from std , whereas mine is.

The tick factor is retrieved using the ::sysconf() call you suggest, and that is guaranteed to return one and the same value throughout the life time of the process.

So the way you use it is as follows:

#include "util/proctime.hpp"

#include <ratio>
#include <chrono>
#include <thread>
#include <utility>
#include <iostream>

int main()
{
  using std::chrono::duration_cast;
  using millisec   = std::chrono::milliseconds;
  using clock_type = rlxutil::combined_clock<std::micro>;

  auto tp1 = clock_type::now();

  /* Perform some random calculations. */
  unsigned long step1 = 1;
  unsigned long step2 = 1;
  for (int i = 0 ; i < 50000000 ; ++i) {
    unsigned long step3 = step1 + step2;
    std::swap(step1,step2);
    std::swap(step2,step3);
  }

  /* Sleep for a while (this adds to real time, but not CPU time). */
  std::this_thread::sleep_for(millisec(1000));

  auto tp2 = clock_type::now();

  std::cout << "Elapsed time: "
            << duration_cast<millisec>(tp2 - tp1)
            << std::endl;

  return 0;
}

The usage above involves a pretty-print function that generates output like this:

Elapsed time: [user 40, system 0, real 1070 millisec]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM