mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
42c047a927
The dominant cost in the reflection serializer was the strict-aliasing reload pattern: every char* write through `string_builder::buffer.get()` forced the compiler to assume `string_builder::position` and `::capacity` may have been clobbered, so it reloaded both members from memory after each byte. perf annotate showed `ldr [position]` and `ldr [capacity]` taking 15-20% of CITM serialization time. This change refactors the reflection atom path to use an internal `writer` struct (buffer pointer + position + capacity + back-ref to string_builder). The writer lives as a stack-local in the top-level append() entry point, threaded through the inlined atom() chain via `writer&`. After SROA, the three fields are register-resident across the entire serialization. The `pos` is never round-tripped through memory between writes — it's a local size_t. This mirrors the pattern Glaze uses (passing `B&& b, auto&& ix` through every helper). The simdjson-specific bit is keeping the public string_builder API intact: only the internal atom() family is refactored to take `writer&`. The top-level append(string_builder&, T) constructs a writer on the stack, threads it through atom(), then syncs the local position back at the end. For string fields specifically, the escape path is also inlined through the writer — the existing `write_string_escaped(input, out)` helper already takes a destination pointer and returns bytes-written, so it composes cleanly with `w.ptr + w.pos`. Without this, sync/ reload around each string write was a real cost on Twitter (-7%). Performance =========== TRUE A/B in single docker invocation, 7 alternating rounds, byte- identical output, all static_reflection_comprehensive_tests pass. Build: clang-p2996 21.0.0git, -O3 -DNDEBUG, aarch64 Linux. CITM Catalog (496 682 bytes): baseline (master): 4356 MB/s patched: 6776 MB/s +55.5% Glaze: 4860 MB/s simdjson now 39.4% AHEAD of Glaze Twitter (81 927 bytes): baseline (master): 6437 MB/s patched: 6452 MB/s +0.2% (break-even) Twitter is string-heavy; the writer's gain over the existing member-based path is small here, but it no longer regresses. Default initial capacity is unchanged at 1024.