nanosleep
Implemented by AI under the supervision of Tallak Tveide.
Sleeps shorter than a millisecond, for the BEAM.
Erlang's timers count in milliseconds. nanosleep runs a small C program as a port, which sleeps for the nanoseconds asked with the operating system's high resolution timers and then answers; the answer wakes the waiting process. The wake-up comes from outside the BEAM rather than from its timer wheel.
{:ok, sleeper} = Nanosleep.open()
{:ok, slept} = Nanosleep.sleep(sleeper, 250_000)
The program wakes within microseconds; its answer then waits for the BEAM to
deliver it, typically some tens of microseconds. For a start on the
microsecond, Nanosleep.sleep_until/3 with spin: wakes that much early and
waits out the rest busy, for that much CPU each time:
deadline = System.monotonic_time(:nanosecond) + 2_000_000
{:ok, late} = Nanosleep.sleep_until(sleeper, deadline, spin: 250_000)
A process that does more than sleep, such as a GenServer, asks with
Nanosleep.wake_after/2 and gets the answer as a message; see Nanosleep.
The program asks to be scheduled in real time where it may: the time constraint policy on macOS, SCHED_FIFO at the lowest real-time priority on Linux (with root, CAP_SYS_NICE or an rtprio limit), and time critical thread priority on Windows. It only ever sleeps and answers.
The program has to outrank what would keep it from waking, and under a BEAM
that is itself scheduled in real time that is the BEAM: at the lowest priority
it waits for every one of the BEAM's threads. So on Linux it keeps one
priority above the BEAM's, and follows it when it changes, as when the BEAM
is given its priority with chrt once it is up. On a BeagleBone Blue with
the BEAM at priority 40, a 10 ms grid was kept within 0.8 ms at p99 with the
program at priority 1, and within 0.4 ms at 41.
Nanosleep.open(priority: 41) asks for that SCHED_FIFO priority instead, and
keeps it. A priority that is refused ends the program, so that it doesn't go
without.
Safe to run beside your application
The C code runs in its own operating system process, not in the BEAM: if it
crashes, the port closes and its owner gets {:closed, status} from
Nanosleep.message/2, and may open another. The program exits when its port
closes or the BEAM goes away.
Installation
{:nanosleep, "~> 0.3"}
It needs a C compiler where it compiles: cc and make on Linux and macOS,
and on Windows MSVC's cl and nmake, from a Developer Command Prompt.
Linux, macOS and Windows (10 version 1803 or later for sleeps shorter than a
millisecond).
How well
How late a process wakes on a fixed grid, measured with bench/compare.exs
(3000 ticks; lateness in microseconds; run it on your own hardware to see yours).
Linux: a Scaleway bare-metal server, 2 × Intel Xeon E5-2620 v2, Debian 13
with its real-time kernel (6.12, PREEMPT_RT), as root, CPU governor
performance and only shallow idle states (cpupower idle-set -D 10):
| 1 ms grid | p50 | p90 | p99 | p99.9 | max |
|---|---|---|---|---|---|
| Erlang timer | 1612 | 1614 | 1621 | 1873 | 3849 |
| nanosleep, not realtime | 64 | 68 | 76 | 99 | 198 |
| nanosleep | 45 | 48 | 65 | 142 | 217 |
| nanosleep, spin 250 µs | 0 | 0 | 0 | 1 | 30 |
| 500 µs grid | p50 | p90 | p99 | p99.9 | max |
|---|---|---|---|---|---|
| Erlang timer | 1879 | 1912 | 1929 | 1955 | 2198 |
| nanosleep, not realtime | 61 | 63 | 67 | 103 | 182 |
| nanosleep | 48 | 53 | 64 | 129 | 204 |
| nanosleep, spin 250 µs | 0 | 0 | 0 | 2 | 6 |
The same machine with the CPU left as installed (governor schedutil, all
idle states): nanosleep 150 / 199 / 219 / 241 / 293 on the 1 ms grid, and
with spin 0 / 0 / 2 / 9 / 50. Deep idle states are what make the wake-up slow.
macOS: an Apple Silicon MacBook in everyday use, four runs. The medians held from run to run; the tail is the desktop's:
| 1 ms grid | p50 | p90 | p99 | max |
|---|---|---|---|---|
| Erlang timer | 1338–1495 | 1357–1525 | 1695–4020 | 3802–10546 |
| nanosleep | 60 | 75–78 | 300–586 | 6411–7111 |
| nanosleep, spin 250 µs | 0 | 0 | 154–273 | 4192–5796 |
The program itself wakes within microseconds; most of what's left is the BEAM
delivering its answer, which spin takes out of the timing for that much CPU
per wake-up. Windows works, and hasn't been measured.
License
Apache-2.0; see LICENSE and NOTICE.