Today's Common Lisp project of the Day is: rate-monotonic.
It is a periodic thread scheduler inspired by RTEMS:
The original documentation on Rate Monotonic Manager's API is not there anymore and page returns 404 error :( All we have is examples from rate-monotonic's readme file. However, I found a new address of this page and created a pull request to fix the readme:
Here is a cite about what monotonic manager does:
The rate monotonic manager provides facilities to implement tasks which execute in a periodic fashion. Critically, it also gathers information about the execution of those periods and can provide important statistics to the user which can be used to analyze and tune the application. ... manager was designed to support application designers who utilize the Rate Monotonic Scheduling Algorithm (RMS) to ensure that their periodic tasks will meet their deadlines, even under transient overload conditions. Although designed for hard real-time systems, the services provided by the rate monotonic manager may be used by any application which requires periodic tasks.
In other words, this library is useful when you have some function which should be called exactly in desired intervals.
For example, we want to do some work every 100 milliseconds. The naive approach will be:
POFTHEDAY> (let ((started-at (get-internal-real-time))) (dotimes (i 11) (format t "i: ~A time: ~A~%" i (coerce (/ (- (get-internal-real-time) started-at) internal-time-units-per-second) 'float)) (force-output) ;; Here we are modelling a payload ;; which takes some time (sleep (random 0.1)) ;; And here we'll try to add a sleep between ;; executions (sleep 0.1))) i: 0 time: 0.0 i: 1 time: 0.166 i: 2 time: 0.323 i: 3 time: 0.431 i: 4 time: 0.624 i: 5 time: 0.776 i: 6 time: 0.95 i: 7 time: 1.142 i: 8 time: 1.286 i: 9 time: 1.46 i: 10 time: 1.616
As you can see, all iterations took 1.6s, however, we wanted each iteration to run every 100ms, and in summary, they should take a 1s.
Here is how rate-monotonic can help here:
POFTHEDAY> (rm:with-timer-period () (let ((p (rm:make-timer-period)) (started-at (get-internal-real-time))) (dotimes (i 11) (rm:period p :ms 100) (format t "i: ~A time: ~A~%" i (coerce (/ (- (get-internal-real-time) started-at) internal-time-units-per-second) 'float)) (force-output) ;; Here we are modelling a payload ;; which takes some time (sleep (random 0.1))) (rm:period-statistics p))) i: 0 time: 0.0 i: 1 time: 0.17 i: 2 time: 0.229 i: 3 time: 0.327 i: 4 time: 0.426 i: 5 time: 0.509 i: 6 time: 0.666 i: 7 time: 0.732 i: 8 time: 0.805 i: 9 time: 0.965 i: 10 time: 1.009 #<RATE-MONOTONIC::STAT :COUNT 10 :MISSED 4 :MIN 27 :AVG 51 :MAX 99>
Now our execution has almost 1 second length. That is it. rm:period works like an adaptive sleep. It pauses thread taking into account a time elapsed from the previous call to rm:period.
Great! In my current project, I have a few places where this library can be used.