exp 168: synchronous uncontended writer mutex fast-path (rejected)#161
Open
danReynolds wants to merge 3 commits into
Open
exp 168: synchronous uncontended writer mutex fast-path (rejected)#161danReynolds wants to merge 3 commits into
danReynolds wants to merge 3 commits into
Conversation
Tested Mutex.tryLock + non-`async` Writer.execute / executeBatch as the request-side counterpart to exp 151's rejected response-side Completer<T>.sync() attempt. Hypothesis: dropping the uncontended `await _mutex.lock()` microtask hop plus the async wrapper's implicit reply await would move the Single Inserts (100 sequential) lane. Paired focused + release runs (3 alternating pairs each): - Single Inserts (100 sequential): 3.099 → 3.153 ms median (+1.7%) - writer_pipelining sequential-awaited (2000 writes): 34.047 → 34.723 ms median (+2.0%) - Concurrent Single Inserts (100 concurrent): 1.231 → 1.135 ms (-7.8%) - writer_pipelining concurrent-burst (10×200): 27.524 → 26.981 ms (-2.0%) - transaction-guardrail (50 tx × 10): 4.611 → 4.770 ms (+3.4%) The primary sequential lanes moved within ±2 % in the wrong direction. The only positive signal (Concurrent Single Inserts) is owned by exp 159 at -58 % to -61 % already. Rejected as request-scheduling change below current signal. No runtime code kept; doc + signals carry the durable evidence so the same idea is not retried without a Dart-runtime change or a workload where main-isolate scheduling dominates writer-side execution. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Records the lesson that exp 151 (response-side) and exp 168 (request-side) produced the same verdict under the same gate; the candidate that follows a scheduling-shape rejection should change the mechanism, not the side. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
3ae685a to
1848f73
Compare
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
After exp 159 + 161, the residual writer/request wall on A11c overlap is still the largest bucket (71.8 %; exp 147). Exp 151 already rejected the response-side
Completer<T>.sync()request-resolution tweak. This experiment tested the matching request-side variant:Mutex.tryLock()plus a non-asyncWriter.execute/Writer.executeBatchthat drops the uncontendedawait _mutex.lock()microtask hop and the wrappingasyncfunction's implicit replyawait. For a serial-await caller (Single Inserts (100 sequential),sequential-awaited), the mutex is always free; both hops are pure scheduling overhead.Acceptance gate (set before running the candidate): focused
sequential-awaited (2000 writes)median moves outside the run-to-run noise band (>5 %), with no regression onconcurrent-burstortransaction-guardrail.Approach
lib/src/mutex.dart: addbool tryLock()— sync acquire when_completer == null, never jumps a parked waiter (preserves FIFO fairness).lib/src/writer/writer.dart: makeWriter.execute/Writer.executeBatchnon-async. Fast path:tryLock()→ sync send via existingexecuteInTransaction/executeBatchInTransaction→unlock()→ return replyFuturedirectly. Slow path staysasync(_executeSlow/_executeBatchSlow).test/transaction_test.dart: rewrite theclose()contention test as two scenarios — (a) writes issued sync-before-close all complete in FIFO order through the worker port (new behavior), (b) a long-running transaction holds the lock while a parked writer wakes on close and sees_closed(still guards the original "do not send to a closed receive port" property).No public API change.
Code reverted before commit since the experiment is rejected — only the writeup, README row, and signal map remain. Cherry-pickable from this PR's git history if a future runner wants the implementation back.
Results
Three paired runs each, baseline / candidate interleaved (per JOURNAL.md "phase-ordered A/B" lesson).
writes.dartwrites.dartsequential-awaited (2000 writes)—writer_pipelining.dartconcurrent-burst (10×200)—writer_pipelining.darttransaction-guardrail (50 tx ×10)—writer_pipelining.dartThe primary sequential lanes moved within ±2 % in the wrong direction across paired runs (inside the per-run noise band). The only positive signal (−7.8 % on Concurrent Single Inserts) is a row exp 159 already drives at −58 % to −61 %.
Outcome
Rejected — below current signal on the primary lane. The hypothesis targeted the
sequential-awaitedshape; the data refutes the premise that theawait _mutex.lock()microtask hop is a measurable fraction of the per-write residual on the canonical lane. The marginal concurrent improvement does not justify the extra slow-path branch plus the behavioral change of sync-before-close writes no longer being rejected.Would reopen if: (a) a future Dart SDK makes
awaitover a resolvedFutureprovably more expensive than today, or (b) a Tracelite profile shows the sequential shape spending materially more wall in main-isolate scheduling spans than in the writer-handle span. Per exp 159 + 161, the next bounded structural candidates for the sequential floor are still cross-call request batching (group commit) or a shared-memory transport.Test plan
dart analyze— no issuesdart test test/transaction_test.dart test/database_test.dart test/diagnostics_test.dart— 93 passed (verified on candidate before revert)dart run benchmark/finalize_experiment.dart --experiment=experiments/168-uncontended-mutex-fastpath.md— greendart run benchmark/experiments/writer_pipelining.dart— 3 paired baseline/candidate runsdart run benchmark/suites/writes.dart— 3 paired baseline/candidate runs🤖 Generated with Claude Code