Exp 127: writer-isolate dispatch wall audit#108
Closed
danReynolds wants to merge 2 commits into
Closed
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds EXP-127 measurement plumbing to split writer-isolate “handler wall” from the inner SQLite-step FFI wall, exposing writer-side dispatch vs SQLite cost in the existing dispatch audit workload family.
Changes:
- Introduces writer-isolate-local
WriterProfileCountersand plumbs a cross-isolate snapshot request/response to read them from the main isolate. - Instruments writer request handlers and write FFI calls (gated by
kProfileMode) to accumulate handler/step timings and counts. - Adds a new profile harness + results markdown and updates experiment metadata/docs (
signals.json, experiments README, generated docs history).
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/src/writer/writer.dart | Adds Writer.fetchProfileSnapshot() request helper for writer counter snapshots. |
| lib/src/writer/write_worker.dart | Adds request/response types for fetching writer counters; instruments _handleExecute/_handleBatch handler wall timing. |
| lib/src/profile_counters.dart | Adds WriterProfileCounters (writer-isolate-local) with snapshot/reset. |
| lib/src/native/resqlite_bindings.dart | Instruments write FFI calls to accumulate writerStepUs (gated by kProfileMode). |
| lib/src/database.dart | Adds Database.writerProfileCounters() public accessor that round-trips to writer isolate. |
| benchmark/profile/audit_workloads.dart | Extends shared audit workload results with writerCounters and captures before/after snapshots to compute diffs. |
| benchmark/profile/writer_dispatch_audit.dart | New EXP-127 audit harness rendering raw counters + derived fractions to markdown. |
| benchmark/profile/results/exp-127-writer-dispatch-aggregate.md | Captured aggregate output from the new harness. |
| experiments/127-writer-dispatch-wall-audit.md | New experiment writeup documenting hypothesis, approach, and results. |
| experiments/signals.json | Updates direction notes/candidates/blocked measurements; adds EXP-127 record. |
| experiments/README.md | Adds EXP-127 row to “In Review” table. |
| docs/experiments/history.json | Regenerated experiment history including EXP-127 entry. |
| docs/benchmarks/devices.json | Regenerated devices metadata timestamp. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Profile-mode WriterProfileCounters split writer-isolate handler wall
from the inner resqlite_execute / resqlite_run_batch FFI step on A11c
baseline / disjoint / overlap and keyed-PK workloads. Resolves the
"writer-isolate wall vs SQLite wall split" blockedOnMeasurement entry
exp 121 left open in the stream-rerun-dispatch direction.
On A11c overlap the writer isolate is only 22-24% of total burst wall
(SQLite step 9-11%, Dart-side dispatch 12-14%); ~76% sits in
main-isolate completion / mutex / IPC. Writer-side dispatch is at the
per-benchmark decision threshold edge; the next dispatch experiment
should target completion-side scheduling, the remaining open
candidate.
Adds:
- lib/src/profile_counters.dart WriterProfileCounters class
- lib/src/writer/write_worker.dart _handle*_ wall + step counters,
FetchWriterProfileRequest,
WriterProfileResponse
- lib/src/writer/writer.dart Writer.fetchProfileSnapshot()
- lib/src/database.dart Database.writerProfileCounters()
- lib/src/native/resqlite_bindings.dart step-time stopwatch around
resqlite_execute and
resqlite_run_batch[_nested]
- benchmark/profile/audit_workloads.dart writerCounters field on
AuditScenarioResult, captured
via writerProfileCounters()
diff per scenario
- benchmark/profile/writer_dispatch_audit.dart exp 127 harness
- benchmark/profile/results/exp-127-writer-dispatch-aggregate.md
- experiments/127-writer-dispatch-wall-audit.md
- experiments/README.md (In Review row)
- experiments/signals.json (direction notes + experiment record)
Existing exp 119 / exp 121 audits inherit the new writerCounters
field unchanged and ignore it. Sanity-checked: parked_total stays at
zero on every workload, reproducing exp 120 / exp 122 invariants.
All 231 tests pass under both default and -DRESQLITE_PROFILE=true.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Gate the per-scenario `db.writerProfileCounters()` round-trips in audit_workloads.dart on `kProfileMode`. The writer-side increments are already kProfileMode-gated, so reading them in release builds only buys isolate round-trips with no signal. Keeps exp 119 / exp 121 audits behaviorally identical when run without -DRESQLITE_PROFILE=true. - Update `AuditScenarioResult.writerCounters` doc to match: empty outside profile mode, populated under -DRESQLITE_PROFILE=true. - Update `Writer.fetchProfileSnapshot` doc: the request handler is unconditional (uniform call shape), but callers only pay the round-trip when they choose to (the audit harness gates). - Add WriterProfileCounters unit tests (snapshot key set + reset contract), matching the existing ProfileCounters test pattern. Locks the public map keys against accidental drift. - Refresh exp 127 doc paragraph on the harness extension to mention the gating. Threads addressed: PRRT_kwDOR-WDb86A0Nn4 (audit_workloads.dart:69 doc) PRRT_kwDOR-WDb86A0Nn- (audit_workloads.dart:168 unconditional cost) PRRT_kwDOR-WDb86A0NoD (writer.dart:180 doc) PRRT_kwDOR-WDb86A0NoJ (profile_counters.dart:214 missing tests) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
9c3cb81 to
3e8063e
Compare
Owner
Author
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.
Hypothesis
Exp 121 left two named blocking measurements in the
stream-rerun-dispatchdirection: completion-side microtaskscheduling cost, and writer-isolate wall vs SQLite step wall split.
This PR ships the second one. Without that breakdown we cannot tell
whether the remaining wall on A11c overlap is dominated by the SQLite
step itself, the writer isolate's Dart-side dispatch path (param
encoding, dirty-tables gather, IPC framing, response construction),
or main-isolate completion work.
The audit was expected to show writer-side dispatch as a small slice
of overlap wall (~10% band), confirming completion-side scheduling as
the next dispatch-area target.
Approach
Added writer-isolate-local profile counters and a round-trip plumbed
through to the audit harness:
lib/src/profile_counters.dart—WriterProfileCountersclass(
writerHandleUs,writerStepUs,writerHandleCount).lib/src/writer/write_worker.dart— wraps_handleExecute/_handleBatchwith stopwatch (gated onkProfileMode); addsFetchWriterProfileRequestandWriterProfileResponse.lib/src/native/resqlite_bindings.dart— wrapsresqlite_executeand
resqlite_run_batch[_nested]FFI calls with stopwatch.lib/src/writer/writer.dart—Writer.fetchProfileSnapshot().lib/src/database.dart—Database.writerProfileCounters().benchmark/profile/audit_workloads.dart—writerCountersfieldon
AuditScenarioResult, captured before/after each scenario viathe new accessor (one extra writer round-trip per scenario).
benchmark/profile/writer_dispatch_audit.dart— new harness usingthe shared A11c / keyed-PK runners and the exp 121 wall convention
(stopwatch stops on the last write).
All increments gated on
kProfileMode; release builds keep thezero-cost contract.
Results
Reader pool size: 4. Four profile passes; values bracket the per-run
band.
Per-write costs:
parked_totalstays at zero on every workload across every run,reproducing exp 120 / exp 122 acceptance signals.
The dominant signal: on A11c overlap the writer isolate is only
22–24% of total burst wall (SQLite step 9–11%, Dart-side dispatch
12–14%). The remaining ~76% sits in main-isolate completion / mutex /
IPC. Writer-side Dart dispatch is at the per-benchmark decision
threshold edge (~14 ms on a 100 ms overlap burst).
Outcome
Accepted (in review) — measurement.
Resolves the
writer-isolate wall vs SQLite wall splitblockedOnMeasuremententry exp 121 left open. Writer-side dispatchjoins invalidation traversal in the "below current decision
threshold, would reopen if a workload elevates dispatch / wall above
~15%" bucket. The next dispatch experiment should target the
remaining open candidate — completion-side microtask scheduling —
where the largest unaccounted slice of overlap wall (~60%) sits.
signals.jsonupdates: removes the resolved measurement-blocker,adds the new "writer-side dispatch slimming" candidate with its
wakeup condition, refreshes the direction's
currentReadandnotesForExperimenters, and adds the exp 127 record. README's InReview table gets the new row.
Test plan
dart analyze --fatal-infos— cleandart test --timeout 60s— 231 tests passdart --define=RESQLITE_PROFILE=true test test/{database,profile_counters,stream}_test.dart— passesdart run benchmark/check_generated_data.dart— up to datedart run benchmark/check_experiment_signals.dart— validparked_totalzero, invalidate fractions in band)🤖 Generated with Claude Code