Repository navigation
defk-25s: break down the per-call cost; a cheaper slice clock on Apple platforms - #78
Merged
Merged
Conversation
…e platforms bench/call_cost.exs measures a kernel call stage by stage on the generated C itself (variants of the bench module's C, each its own library) against a bare NIF floor (bench/call_cost_nif.c). A kernel without a loop costs 11-13 ns, 6 of them the BEAM's NIF call and the terms; a yielding kernel cost 37 ns, 22 of them the ERTS clock read at entry. The slice clock (defk_now_us) now reads mach_absolute_time on Apple platforms, converted with the timebase read once per library at load; other platforms keep enif_monotonic_time. A tiny yielding call is 21 ns. The forced-yield test path still reads the ERTS clock once per yield: that read advances the scheduler's last_monotonic_time, which its timer wheel needs, and forced yields charge no reductions. The small-input rows are re-measured and stored: ws_masking 1.5x -> 1.77x, geohash one point per call 1.3x -> 1.62x; poor_tiny_calls and utf8_valid within noise. docs/performance.md 5.13 holds the breakdown; 5.1, 4.9, 8.2, docs/project.md and docs/beam-nifs.md point at it.
DanielLuning
pushed a commit
that referenced
this pull request
Oct 21, 2026
…cost, worktree ../defk-25s). Breakdown in docs/performance.md 5.13: a kernel without a loop 11-13 ns (6 of it the BEAM's NIF call), a yielding kernel 37 -> 21 ns with the mach_absolute_time slice clock on Apple platforms. Four small-input rows re-measured and stored. Local mix ci was still running when the PR opened; its result goes on the PR. Waits for the owner's merge."
Owner
Author
|
Local |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
bench/call_cost.exsandbench/call_cost_nif.c: the base cost of a kernel call, stage by stage, measured on the generated C itself. The bench module's C is varied with string replacements (nosigsetjmp, no fault-containment stores, no entry clock read, no slice init), each variant built and loaded as its own library, against a bare-NIF floor.priv/defk_runtime.h: the slice clock isdefk_now_us. On Apple platforms it readsmach_absolute_time, converted with the timebase read once per library at load; other platforms keepenif_monotonic_time. The forced-yield test path still reads the ERTS clock once per forced yield (see below).docs/performance.md5.13 holds the breakdown; 5.1, 4.9 and 8.2 point at it;docs/project.mdhas a dated line under the results snapshot;docs/beam-nifs.mdnames the clock. The four small-input rows are re-measured and stored (examples/scoreboard.json, README tables regenerated).Results
Median of 9 interleaved rounds of 1M calls, minus the empty loop, M-series Mac, quiet (other processes at 0.1-0.8 cores):
add/2(no loop)enif_monotonic_time/clock_gettime/mach_absolute_timeover a bare NIFThe entry clock read was 22 ns of a tiny yielding call. A kernel without a loop adds about 5 ns to a plain NIF that decodes the same terms (the
sigsetjmp2.5, the fault-containment stores 1.2, the rest 1.6): nothing worth cutting there. The hint BIF (about 9 ns) stays: it is what spreads short calls (4.9).Stored rows (quiet, 2026-10-19 -> now):
ws_maskingdefk 1.231 -> 1.129 ms per 10,000 frames (123 -> 113 ns a frame; 1.46x -> 1.47x because the Elixir side also measured faster this session),geohash_crcone point per call 1.953 -> 1.657 ms (1.29x -> 1.58x),poor_tiny_calls53 -> 45 ns a call (no loop, so nothing changed on its path: session spread),utf8_validunchanged.bench/tick_gap.exson one scheduler: yielding kernel p99 2.26 ms, worst 2.27-2.44 ms, the same as the snapshot's 2.25-2.3.Found on the way: an ERTS clock read stores the time in the scheduler's data (
erts_get_monotonic_timesetslast_monotonic_timeand resetscheck_time_reds), and the scheduler bumps its timer wheel from that stored time. Forced yields charge no reductions, so with the cheap clock alone a test process that slept 1 ms on the kernel's scheduler woke only when the kernel ended (bitstring_test, "a process killed mid-build" timed out). The forced path now reads the ERTS clock once per forced yield, as the old entry read did at every re-entry. A normal yield charges reductions throughenif_consume_timeslice, so the scheduler reads its own clock there; the tick-gap numbers above confirm it.How it was checked
mix testtwice after the header changes (2305 passed; the one failure above found and fixed between them),mix cion the M-series Mac (running at the time of opening; the result follows as a comment),bench/tick_gap.exs, and the four examples throughmix defk.examples --bench --only ... --snapshot. The stored rows' stamps name the measured commit (0377111); the second commit is docs wording only.CHANGELOG.mdentry says so under "no promise changes", added for visibility: drop it if the changelog is for promises only.test/stability/? No.