Current section
Files
Jump to
Current section
Files
README.md
# nanosleep
[](https://github.com/tallakt/nanosleep/actions/workflows/ci.yml)
[](https://hex.pm/packages/nanosleep)
[](https://hexdocs.pm/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.
```elixir
{: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:
```elixir
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.
On Linux, `Nanosleep.open(priority: 41)` asks for that SCHED_FIFO priority
instead of the lowest. 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.
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.
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
```elixir
{:nanosleep, "~> 0.2"}
```
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.