simdjson-rust 0.4.0-alpha.2

Rust bindings for the simdjson project.
Documentation
# JSON-to-struct profiling, 2026-09-07

The measured workload does not show an obvious large, low-risk optimization.
The extra tape scan accounts for about 7% of CPU samples; Rust UTF-8 conversion
accounts for 8–10%. Both are real costs, but eliminating either needs a sound
replacement for the current safety checks. These percentages are not measured
speedups. Caching a tape view does not help a document that is deserialized once.

## Workload and environment

- Library revision: `6ada4b0`; only the profiling harness was expanded.
- Debian 13, x86-64 KVM guest, Xeon Platinum 8168, four visible CPUs.
- Rust 1.98.1, GCC 14.2, simdjson 4.6.4. The sampled C++ path was `haswell`.
- Release optimization, fat LTO, native CPU tuning for Rust and C++, debug
  symbols and frame pointers. This is not a default-feature portability benchmark.
- Synthetic JSON containing 1, 10 or 500 user structs: 161, 1,312 or 80,934 bytes.
  Fields include owned `String`s, integers, floating-point numbers, booleans and
  `Vec<u64>`; text is ASCII. No intermediate `tape::Value` tree is constructed.
- Data generation and equivalence checking are outside the timed region.
  Every invocation warms up 100 times and consumes results via `black_box`.
  Full and Serde-only timings include dropping the resulting owned structs.
- Seven runs per mode/size, serial and shuffled with seed 42. Iteration counts
  were calibrated toward 150 ms per run. Each run uses a separate process.
- The VM showed substantial timing variation. Treat small differences as
  inconclusive; these inputs are not a substitute for application data.

## CPU samples: full parse with parser reuse

`perf_event_paranoid=3` restricts hardware profiling in this environment.
Instead, gperftools 2.16's signal-based CPU profiler sampled two independent
60,000-iteration runs at a requested frequency of 1,000 Hz. They delivered
4,915 and 5,633 samples. Timings below were collected separately without the
profiler. Percentages are inclusive call-stack shares, not wall-clock shares.

| Work | Run 1 | Run 2 |
| --- | ---: | ---: |
| C++ parse, `simdjson_sys::dom::parser_parse` | 33.4% | 31.8% |
| Tape view / initialized string-prefix scan | 6.8% | 6.8% |
| Serde `from_tape`, including field allocation and UTF-8 checks | 54.1% | 54.0% |
| Destruction of the resulting owned struct | 5.2% | 6.9% |

The remaining approximately 0.4–0.6% is the rest of the parse wrapper and
rounding. This includes input copying and padding. The difference is calculated
from the enclosing parse frame minus C++ parse and tape-view frames; it is not
a separately sampled memcpy-only cost.

Within the categories above (do **not** add these to the table):

- `core::str::converts::from_utf8`: 10.2% / 8.1%, including validation itself.
- `malloc` and `free` together: approximately 17% in each run, distributed
  across conversion and result destruction. Internal libc symbols were stripped,
  so finer attribution inside the allocator is not reliable.

## Stage timings

Medians in microseconds; stages measured in separate loops are **not additive**.
A cached tape in `serde`/`view` mode has a different cache state from a fresh parse.

| Mode | 161 B | 1,312 B | 80,934 B |
| --- | ---: | ---: | ---: |
| Full, reused parser (`reuse`) | 0.763 | 6.252 | 418.997 |
| Full, fresh parser (`fresh`) | 1.283 | 6.125 | 416.606 |
| Full, caller-padded input (`padded`) | 0.693 | 6.539 | 415.450 |
| Safe parsing plus tape view (`parse`) | 0.271 | 2.219 | 160.938 |
| C++ parsing only, pre-padded input (`ffi`) | 0.221 | 1.826 | 117.037 |
| Tape view only (`view`) | 0.042 | 0.384 | 26.131 |
| Existing tape to struct, including drop (`serde`) | 0.327 | 3.301 | 204.292 |

For the large input, full reuse ranged from **379–589 µs**, padded parsing
from **322–508 µs**, and the scan alone from **25–38 µs**. The padded and
ordinary full paths overlap substantially; there is no demonstrated end-to-end
win from avoiding the input copy here. The small-input fresh-parser penalty is
more visible; reuse is an API usage choice, not a new parser optimization.

[Individual runs and calibration counts](2026-09-07-timings.json) also include
an incidental `serde_json` control. Different synthetic schemas and VM noise
make these measurements unsuitable for a general parser ranking.

## Implications

1. Do not spend effort caching tape views for this one-deserialization-per-parse
   workload. Reducing the scan would need to capture the initialized prefix while
   C++ is already building the tape, or use another sound representation.
2. Do not remove UTF-8 checks from the public arbitrary-tape API. A future trusted
   representation could be investigated, but requires a complete safety argument.
3. Input copying is not a priority for this workload. Prefer the convenient safe
   API unless real application measurements demonstrate otherwise.
4. Owned field allocation and generic Serde traversal remain substantial. Using
   borrowed fields would change the consuming struct/lifetime contract; it is
   not a transparent optimization of the existing owned-struct workload.
5. There is no evidence here to justify delaying the release for performance
   work. Revisit using representative application data if it becomes a bottleneck.

## Reproduce

From the repository root, with Rust/C++ build tools installed:

```sh
CARGO_PROFILE_RELEASE_DEBUG=1 \
RUSTFLAGS='-C target-cpu=native -C force-frame-pointers=yes' \
CXXFLAGS='-g -fno-omit-frame-pointer' \
cargo build --release --example perf_sample --features native

python3 scripts/profile_stages.py --output /tmp/simdjson-profile-timings.json

# A single diagnostic mode: iterations, mode, number of users.
target/release/examples/perf_sample 20000 reuse 500
```

On Linux with gperftools' `libprofiler` and `google-pprof` available:

```sh
LD_PRELOAD=/path/to/libprofiler.so.0 \
CPUPROFILE=/tmp/simdjson-struct.prof CPUPROFILE_FREQUENCY=1000 \
target/release/examples/perf_sample 60000 reuse 500

google-pprof --text --cum --nodecount=100 \
  target/release/examples/perf_sample /tmp/simdjson-struct.prof
```

Run sampling separately from the timing sweep. Do not compile or run other
benchmarks concurrently. The profiler's requested frequency may exceed the
number of signals delivered by the operating system.