Skip to content

Record exp 159: Row.containsKey identity fast path (rejected)#154

Closed
danReynolds wants to merge 1 commit into
mainfrom
danreynolds/stoic-panini-3b4ab2
Closed

Record exp 159: Row.containsKey identity fast path (rejected)#154
danReynolds wants to merge 1 commit into
mainfrom
danreynolds/stoic-panini-3b4ab2

Conversation

@danReynolds

Copy link
Copy Markdown
Owner

Hypothesis

Experiment 158 added a schema-name identity fast path to RowSchema.indexOf and saw row_map_facade hot-lookup drop 10.750 → 5.136 ms (-52%). It intentionally left Row.containsKey on the original _indexByName.containsKey(key) path; the containsKey row median moved 17.720 → 17.701 ms (neutral). Exp 159 tested whether routing Row.containsKey through the same indexOf fast path would produce a measurable win on the same benchmark, given the change is behavior-preserving and trivially small.

Approach

One-line swap inside Row.containsKey:

bool containsKey(Object? key) =>
    key is String && _schema.indexOf(key) >= 0;

Reuses the existing 32-column identity scan + private HashMap<String, int> fallback established by exp 158 (RowSchema.indexOf unchanged). No public API changes, no transfer surface changes. A pre-run sanity check confirmed identical("updated_at", schema.names[5]) == true on the benchmark schema, so the identity fast path actually fires when measured.

Results

Three paired runs of dart run benchmark/experiments/row_map_facade.dart (8-column schema, 500,000 inner iterations per case):

Run Baseline containsKey row (ms) Candidate containsKey row (ms)
1 15.329 12.377
2 15.231 14.707
3 13.930 15.101
Metric Value
Baseline median 15.231 ms
Candidate median 14.707 ms
Delta -3.4%
Baseline range 13.930 – 15.329 ms
Candidate range 12.377 – 15.101 ms

The candidate range fully overlaps the baseline range and the run-3 candidate (15.101 ms) is above the run-3 baseline (13.930 ms). The nominal -3.4% median delta is smaller than per-run variance on both sides.

Outcome

Rejected — below the noise floor on row_map_facade. HashMap<String, int>.containsKey is already very fast on canonical-string keys because Dart caches String.hashCode, so the identity-scan replacement saves only nanoseconds per call. The candidate also adds a small theoretical downside on non-canonical keys (an up-to-32 element identity scan before the HashMap fallback).

Would reopen if a workload appears where containsKey on canonical-and-present column names is a material fraction of wall time (e.g. a streaming consumer filtering rows by optional-column presence per emission). The runtime change has been reverted before merge; only the experiment writeup, README row, signals.json update, and regenerated docs/experiments/history.json land here.

This is not evidence against exp 158's indexOf fast path — that still stands. It only bounds the containsKey-side extension.

Test plan

  • dart pub get in the worktree
  • dart analyze lib/No issues found!
  • dart test test/database_test.dart — 49/49 pass (includes row.containsKey('id') / row.containsKey('name') / row.containsKey('nonexistent') assertions on the candidate before revert)
  • Focused identity check: identical('updated_at', schema.names[5]) == true
  • Focused row_map_facade A/B: 3 baseline + 3 candidate runs (table above)
  • dart run benchmark/finalize_experiment.dart --experiment=experiments/159-row-containskey-identity-fast-path.md

🤖 Generated with Claude Code

Tested whether extending exp 158's RowSchema.indexOf identity scan to
Row.containsKey produces a measurable win on row_map_facade. Three paired
runs of the focused benchmark moved the containsKey row median 15.231 ->
14.707 ms (-3.4%) under the candidate, but the candidate range
(12.377-15.101 ms) fully overlaps the baseline range (13.930-15.329 ms),
so the change collapses to noise.

HashMap<String, int>.containsKey is already very fast on canonical-string
keys (Dart caches String.hashCode), so the identity-scan replacement
saves only nanoseconds per call. The row.dart change has been reverted
before merge; only the experiment writeup, README row, signals.json
update, and regenerated docs/experiments/history.json land here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@danReynolds

Copy link
Copy Markdown
Owner Author

Heads-up: experiment number 159 is now taken on main — PR #153 (exp 159: writer request pipelining + persistent reply port) just merged with experiments/159-writer-pipelining.md and the matching signals.json/README rows. This record PR will need renumbering (next free looks like 161, since the open #155 claims 160) before merge to keep the monotonic-numbering convention and avoid clobbering history.json entries.

@danReynolds danReynolds deleted the danreynolds/stoic-panini-3b4ab2 branch June 16, 2026 15:14
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