Skip to content

perf(set): single-probe ttak_set_add (and fix inverted presence check) - #99

Merged
gg582 merged 1 commit into
mainfrom
perf/set-add-single-probe
Oct 13, 2026
Merged

gg582 merged 1 commit into
mainfrom
perf/set-add-single-probe

Conversation

@gg582

@gg582 gg582 commented Oct 13, 2026

Copy link
Copy Markdown
Member

Summary

ttak_set_add did a full get probe and then a full put probe per call — two hash computations and two table walks for every insert. While reading the code I also found the presence check was inverted: keys were only re-stored when already present, so new keys were never added. This is a live bug — tutorials/07-set-container prints contains beta? no immediately after adding "beta" on current main.

Changes

  • src/ht/table.c: extract the insert logic into a shared table_insert() core with a replace flag; ttak_table_put() keeps its exact old behavior, and a new ttak_table_put_if_absent() performs a single probe and reports whether it inserted.
  • internal/ttak/table_internal.h (new): declaration of ttak_table_put_if_absent, following the existing internal-header convention (e.g. mem_internal.h).
  • src/container/set.c: ttak_set_add now calls ttak_table_put_if_absent — one probe per add, duplicates stay no-ops (existing value never replaced, no destructor invoked).
  • bench/bench_set_add_dup.c (new): the A/B benchmark used for the numbers below.

Benchmark (A/B against main built in a clean worktree)

Workload: prefill a set with 100k keys via ttak_table_put (identical table state for both builds, since old ttak_set_add cannot insert), then 1M ttak_set_add attempts — 90% duplicates, 10% rotating fresh keys. gcc -O3 -march=native, best of 5 repeats, interleaved runs:

build ns/add (run 1) run 2 run 3 run 4
main (old) 29.81 30.31 29.81 29.78
this PR 22.10 27.58* 21.96 22.05

* single outlier run; all other new-build runs cluster at 22.0–22.1. ~26% faster, stable across runs, well above the 5% bar. The win comes from halving probes on duplicate-dominant adds.

Verification

  • make test (gcc): all tests pass.
  • Tutorial 07 now prints the documented output (contains beta? yes, contains alpha? no after removal).

Notes

  • ttak_table_put semantics are unchanged (replace + val_free on existing values), so ttak_map and all other table users are unaffected.
  • Remaining known candidates (pool allocator division, pocket remote-lock) were left untouched — this was the clearest win.

ttak_set_add previously performed a full get probe followed by a full
put probe per call. It also had an inverted presence check, so keys
were only (re)stored when already present and new keys were never
added; the set-container tutorial observed 'contains beta? no'
immediately after adding 'beta'.

Refactor the insert path in table.c into a shared table_insert core
with a replace flag, and expose ttak_table_put_if_absent() through a
new internal header. ttak_set_add now resolves every add, duplicate or
fresh, with a single probe while keeping put-if-absent semantics.

A/B benchmark (1M add attempts over a 100k-element set, duplicates
dominating, best of 5, gcc -O3 -march=native):

    before: 29.8-30.4 ns/add
    after:  22.0-22.1 ns/add   (~26% faster)

Full test suite passes; tutorial 07 now prints the documented output.
@gg582
gg582 merged commit ac5536a into main Oct 13, 2026
4 checks passed
@gg582
gg582 deleted the perf/set-add-single-probe branch October 13, 2026 04:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant