Skip to content

perf: SIMD-accelerate JSON string paths (from_json/to_json) - #13

Merged
khanaffan merged 1 commit into
mainfrom
perf/simd-json-string-paths
Jul 2, 2026
Merged

perf: SIMD-accelerate JSON string paths (from_json/to_json)#13
khanaffan merged 1 commit into
mainfrom
perf/simd-json-string-paths

Conversation

@khanaffan

Copy link
Copy Markdown
Owner

Summary

Adds 128-bit SIMD (ARM NEON + x86 SSE2, with a scalar fallback) to the two byte-at-a-time string loops in the msgpack⇄JSON conversion — the one place in this extension where raw byte throughput actually dominates.

  • mpJpParseString (msgpack_from_json): SIMD-scan for "/\, then bulk-copy the safe run in a single append.
  • mpJsonEscapeStr (msgpack_to_json / msgpack_pretty): SIMD-scan for "/\/control bytes, then jump straight to the next byte that needs escaping.

New helpers mpScanStr/mpScanEsc:

  • Use full 16-byte loads only (i+16<=len) so we never read past the buffer; the short tail is handled scalar.
  • Use only the always-available baseline ISA (NEON on arm64, SSE2 on x86-64) → no runtime CPU dispatch.
  • A cheap first-byte guard avoids any regression on escape-dense input.
  • -DMSGPACK_DISABLE_SIMD forces the portable scalar path.

Why not extract/set?

MessagePack is a length-prefixed TLV format, so element navigation (mpSkipOne/mpLookup) is a serial pointer-chase that can't be vectorized; key/buffer ops already use libc SIMD memcmp/memcpy. Those paths are intentionally left untouched.

Measured gain

arm64/NEON, Release, ~4 KB strings, A/B vs MSGPACK_DISABLE_SIMD:

case scalar SIMD speedup
from_json, no escapes ~2670 ns ~579 ns ~4.6x
to_json, no escapes ~2590 ns ~510 ns ~5.0x
to_json, all-escapes (worst) ~8500 ns ~8500 ns ~1.0x (parity, no regression)

Small strings are unchanged (bound by SQLite call overhead) — as expected.

Correctness

  • 16/16 ctest pass; fuzz corpus pass.
  • 15 hand-crafted boundary cases (escapes at 16-byte chunk offsets 15/16/17, control bytes, NUL, tails, empty): SIMD output byte-identical to scalar + round-trip OK.
  • 6000-case deterministic differential fuzz (random lengths/offsets/specials): SIMD output byte-identical to scalar.
  • x86-64 SSE2 branch cross-compiles clean; MSVC-safe (_BitScanForward).

Changes

  • src/msgpack.c — SIMD scanners + wired into the two JSON string loops.
  • tests/bench_msgpack_vs_json.c — large-string benchmark rows (plain + all-escapes).

Add 128-bit SIMD (ARM NEON + x86 SSE2, scalar fallback) to the two
byte-at-a-time string loops in the msgpack<->JSON conversion, the one
place in the extension where raw byte throughput dominates:

- mpJpParseString (from_json): SIMD scan for '"'/'\' then bulk-copy the
  safe run in a single append.
- mpJsonEscapeStr (to_json/pretty): SIMD scan for '"'/'\'/control bytes
  then jump straight to the next byte needing escaping.

New helpers mpScanStr/mpScanEsc use full 16-byte loads only (no
out-of-bounds over-read), a scalar tail, and only the always-available
baseline ISA (no runtime CPU dispatch). A cheap first-byte guard avoids
any regression on escape-dense input. Define MSGPACK_DISABLE_SIMD to
force the portable scalar path.

MessagePack navigation (extract/set/remove/type) is a serial
length-prefixed pointer-chase that cannot be vectorized, and key/buffer
ops already use libc SIMD memcmp/memcpy, so those are left untouched.

Measured on arm64/NEON, ~4 KB strings (A/B vs MSGPACK_DISABLE_SIMD):
- from_json, no escapes: ~2670 -> ~579 ns/op  (~4.6x)
- to_json,   no escapes: ~2590 -> ~510 ns/op  (~5.0x)
- to_json,   all escapes (worst case): parity, no regression
Small strings are unchanged (bound by SQLite call overhead).

Correctness: 16/16 ctest pass; a 6000-case deterministic differential
fuzz and 15 boundary edge cases (escapes at 16-byte chunk offsets,
control bytes, tails) produce byte-identical output to the scalar path.
SSE2 branch cross-compiles clean and is MSVC-safe (_BitScanForward).

Also adds large-string rows to bench_msgpack_vs_json.c.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@khanaffan
khanaffan merged commit d2316ac into main Jul 2, 2026
6 checks passed
@khanaffan
khanaffan deleted the perf/simd-json-string-paths branch July 2, 2026 14:51
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