commit cd997d0fecd8f80e5c141064ec4c1c4b5ff2f26a
parent e665229aefa465755b17d605f20610d6cf4bd52e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 10 Jun 2026 22:56:09 -0700
perf(core): hoist table/entries bases out of the intern probe loop
pool_intern_slice runs once per identifier/keyword token (3-7% self-time on
every axis). The probe reloaded p->table and p->entries from the Pool struct on
each iteration (confirmed in disassembly). Both are stable across a probe (a
grow only happens after a miss), so hoist them into locals — one fewer dependent
load per probe step. Byte-identical (Sym identity unchanged); verified
test-parse + test-cg-api green.
Deliberately not done here: word-at-a-time intern hash (measured SLOWER for the
short identifiers real code uses), lower load factor (probe length already 1.3-
1.9), and the inline-prefix cache (negligible) — all refuted by measurement. The
image-id FNV->HW-SHA swap is a real symbol-count win but changes every
executable's LC_UUID/build-id bytes, so it is left as a deliberate, separately
gated change.
Diffstat:
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/core/pool.c b/src/core/pool.c
@@ -111,9 +111,16 @@ Sym pool_intern_slice(Pool* p, Slice in) {
h = fnv1a(s, len);
mask = p->cap - 1;
i = h & mask;
- while ((sym = p->table[i]) != 0) {
- if (sym_eq(&p->entries[sym], s, len, h)) return sym;
- i = (i + 1) & mask;
+ /* Hoist the table/entries bases: they are stable across the probe (a grow
+ * only happens after a miss, below), so the compiler need not reload them
+ * from the Pool each iteration. This is the per-identifier intern probe. */
+ {
+ const Sym* table = p->table;
+ const PoolEntry* ents = p->entries;
+ while ((sym = table[i]) != 0) {
+ if (sym_eq(&ents[sym], s, len, h)) return sym;
+ i = (i + 1) & mask;
+ }
}
/* Not found: allocate a new entry. The stored buffer carries a trailing
* NUL so a returned slice's pointer can be handed to a NUL-terminated