Experiment 235: Resume SWAR escape scan after each dirty chunk

Date: 2026-07-21

Status: Accepted

Direction:result-transfer-shape

Benchmark Run: none — focused A/B

(benchmark/experiments/select_bytes_text_string_reserve.dart),

two rounds of order-flipped passes on a quiet box; no release-suite run

because the changed path is specifically the TEXT-cell JSON string escaper

and the focused harness isolates safe, sparse-escape, dense-escape, and

late-escape shapes directly.

Problem

resqlite_json_write_string — the SQLITE_TEXT arm of the selectBytes

row serializer — escapes each TEXT cell with a SWAR fast scan that inspects

8 bytes at a time and skips whole clean chunks without touching them

byte-by-byte (the common case: text with no JSON-escapable bytes). But the

scan was structured as a while (i + 8 <= len) SWAR loop that breaks to a

trailing byte-by-byte for loop on the first chunk containing an

escapable byte — and never returns to SWAR.

That means a single escape early in an otherwise-safe value forces the entire

remaining tail through the one-byte-at-a-time loop. A 256-byte log line with a

newline near the front, a multi-line chat message, a paragraph of prose with a

stray quote, or JSON stored as TEXT all hit this: the value is >95% safe bytes,

but the scanner crawls almost all of it one byte at a time because it saw one

escape early. Exp 230 sped up the *safe-prefix

scan* with NEON and was rejected at its 256B cutoff, but it explicitly "did not

restart SIMD after an escape" — so the resume inefficiency was never addressed.

Hypothesis

If the SWAR skip is re-entered after each dirty chunk instead of abandoned

permanently, a sparse escape only downgrades the single 8-byte chunk that

actually contains it; the clean spans on either side keep skipping 8 at a time.

The win should reproduce on long TEXT with sparse escapes (multi-line text),

while the pure-safe common case stays byte-identical (the inner scan is

unchanged) and dense-escape text pays at most a small per-dirty-chunk cost.

Acceptance criterion, declared before measuring: the load-bearing row is the

256B sparse-newline lane (a \n roughly every 80 bytes — realistic

word-wrapped text). It must reproduce a clear win across the order flip, with

the pure-safe long-ASCII guard neutral. Larger and sparser lanes are

confirmation; dense-escape lanes are guards whose cost is recorded, not tuned

around.

Approach

The scanner becomes an outer loop wrapping the original tight SWAR skip:

 while (i < len) { while (i + 8 <= len) {            // baseline's inner skip, 1 bounds check/chunk ...SWAR classify 8 bytes... if (dirty) break; i += 8; } if (i >= len) break; int block_end = i + 8 <= len ? i + 8 : len; for (; i < block_end; i++) {      // handle one dirty chunk (or the tail) ...emit escape or accumulate safe byte... } } 

Key design points, each load-bearing for a guard result:

An earlier candidate that used one while (i < len) with an inner

if (i + 8 <= len) paid a second bounds check on every clean chunk and

regressed the pure-safe and late-escape guards +7%; folding the skip back into

a tight inner while collapsed that to noise.

then SWAR resumes — one SWAR probe per dirty chunk, not per byte. The

first candidate re-probed SWAR after every safe byte inside a dirty region and

regressed dense-escape +24%.

handling are unchanged. Output is byte-identical; the existing

native_encoder_diff_test.dart oracle (escapes injected at positions

{0, 15, 16, len−1} across lengths 0–1024, compared byte-for-byte against

dart:convert) passes unchanged.

The harness gains two sparse-escape modes it lacked: sparseNewline (a \n

every ~80 bytes) and sparseEarly (one escape near the start, then a long safe

tail). The pre-existing escaped (dense) and lateEscape (escape at the very

end) lanes stay as guards — neither exercised the resume inefficiency, which is

why the win was invisible before.

Results

Two rounds of order-flipped passes (5 BASE / 4 CAND medians), Apple Silicon.

Full data in

benchmark/results/2026-07-21T14-05-36Z-exp235-json-escape-swar-resume.md.

LaneΔ (order-flipped)
pure-safe long ASCII 256B−3.4%common case — parity
pure-safe very long ASCII 1KiB−1.7%common case — parity
sparse-newline 256B−23.4%load-bearing
sparse-newline 1KiB−31.4%confirmation
sparse-early 256B−44.7%confirmation
sparse-early 1KiB−44.1%confirmation
dense-escape 24B / 96B+2.1% / +0.1%guard — noise
dense-escape 256B+6.0%guard — pathological only
late-escape 96B / 256B+2.0% / +4.5%guard — noisy ~parity

Realistic multi-line TEXT encodes ~1.3×–1.8× faster, and the extreme

one-early-escape case is nearly 2× faster — because the old scanner turned a

single early escape into a full-length byte crawl and the new one does not. The

pure-safe path (the dominant real workload) is provably unchanged and measures

as parity. The lone real cost is dense-escape 256B at +6.0%: text where ~⅓ of

bytes need escaping pays one extra SWAR probe per all-dirty chunk. That

distribution is not natural text, is not in the release suite, and the sparse

win dominates the realistic mix.

Outcome

Accepted (in review). A contained ~−23% (load-bearing) to −44%

selectBytes TEXT-encoding win on sparse-escape values — the common shape of

multi-line and lightly-quoted text — with the escape-free common path unchanged

and byte-identical output. The cost is ~+6% on pathological dense-escape 256B

text only.

Would reopen the dense regression only if a real workload or profile shows

dense-escape TEXT (≥⅓ escapable bytes) is a hot path; the natural next TEXT-side

mechanism remains the signals-named SIMD escape scan for the safe-prefix scan

speed (distinct from this resume fix), gated by the same focused harness.