From e23ea2460576c22582ee08aaf2f5681a656aed51 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Thu, 8 Oct 2026 19:35:42 -0700 Subject: [PATCH] Keep number-key map transitions out of line The number-key insert inlined a second copy of the hashed word insert and every index transition (1.7 KB on arm64). One out-of-line word insert now serves the generic dispatcher and the number path, and entering, growing and leaving the direct index stay out of line, leaving the insert at 0.5 KB. --- packages/runtime/src/scr_map.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/runtime/src/scr_map.c b/packages/runtime/src/scr_map.c index 85cc73b53..8f0af7315 100644 --- a/packages/runtime/src/scr_map.c +++ b/packages/runtime/src/scr_map.c @@ -475,7 +475,7 @@ static void scr_map_dense_alloc(ScrMap *m, size_t n) { * take an absent key: index it directly when every live key and the * incoming key fit a compact table, keep hashing otherwise. One scan per * growth step keeps this amortized constant per insertion. */ -static void scr_map_enter_dense(ScrMap *m, double incoming) { +static __attribute__((noinline)) void scr_map_enter_dense(ScrMap *m, double incoming) { size_t limit = scr_map_dense_limit(m->nlive + 1), top, i; if (m->nentries >= UINT32_MAX - 1 || !scr_map_dense_slot(incoming, limit, &top)) return; for (size_t e = 0; e < m->nentries; e++) { @@ -491,7 +491,7 @@ static void scr_map_enter_dense(ScrMap *m, double incoming) { } /* Extend the table to cover a new integral key while it stays compact. */ -static bool scr_map_grow_dense(ScrMap *m, double k, size_t *out) { +static __attribute__((noinline)) bool scr_map_grow_dense(ScrMap *m, double k, size_t *out) { size_t i; if (!scr_map_dense_slot(k, scr_map_dense_limit(m->nlive + 1), &i)) return false; size_t old = m->ndense, n = scr_map_dense_size(i); @@ -502,7 +502,7 @@ static bool scr_map_grow_dense(ScrMap *m, double k, size_t *out) { } /* Switch to the bucket table; cached entry hashes are already complete. */ -static void scr_map_leave_dense(ScrMap *m) { +static __attribute__((noinline)) void scr_map_leave_dense(ScrMap *m) { free(m->dense); m->dense = NULL; m->ndense = 0; @@ -839,6 +839,13 @@ static void scr_map_set_str_slot(ScrMap *m, uint64_t hash, uint64_t key, uint64_ scr_map_set_with(m, hash, key, val, SCR_MAP_LOOKUP_STR); } +/* One out-of-line copy of the word-key insert serves both the generic + * dispatcher and the number-key path below. */ +static __attribute__((noinline)) void scr_map_set_word(ScrMap *m, uint64_t hash, uint64_t key, + uint64_t val) { + scr_map_set_with(m, hash, key, val, SCR_MAP_LOOKUP_WORD); +} + /* Whether appending one more entry would grow the lookup index rather than * reuse tombstones: scr_map_reserve_append compacts a linear map with a * dead entry, or a bucket table at most half live, whenever no iteration @@ -883,7 +890,7 @@ static void scr_map_set_f64_slot(ScrMap *m, uint64_t key, uint64_t val) { } scr_map_leave_dense(m); } - scr_map_set_with(m, scr_map_hash_word(key), key, val, SCR_MAP_LOOKUP_WORD); + scr_map_set_word(m, scr_map_hash_word(key), key, val); } static void scr_map_set(ScrMap *m, uint64_t hash, uint64_t key, uint64_t val) { @@ -893,7 +900,7 @@ static void scr_map_set(ScrMap *m, uint64_t hash, uint64_t key, uint64_t val) { } switch (scr_map_lookup_kind(m)) { case SCR_MAP_LOOKUP_STR: scr_map_set_str_slot(m, hash, key, val); return; - case SCR_MAP_LOOKUP_WORD: scr_map_set_with(m, hash, key, val, SCR_MAP_LOOKUP_WORD); return; + case SCR_MAP_LOOKUP_WORD: scr_map_set_word(m, hash, key, val); return; default: scr_map_set_with(m, hash, key, val, SCR_MAP_LOOKUP_GENERIC); return; } }