perf(cli): avoid cloning JSON results into an intermediate tree
CLI JSON results currently clone the entire payload into serde_json::Value before pretty-printing it. Serialize a borrowed success envelope directly. Preserve the parsed JSON, pretty formatting and null fallback on serialization failure. Object key order follows the serializer; the previous intermediate map's ordering also varied with serde_json/preserve_order. The CLI documents keys and values, not key order.
Release comparison on one Linux runner without serde_json/preserve_order for the first table, base algorithm copied verbatim from e6e3020 versus the production helper. Two independent runs, 12 alternating paired samples per scenario, 10 serializations per timing sample. Allocation accounting runs separately with counters disabled during timing. Synthetic MessageDto inputs include UTF-8 and escaped characters.
| Messages / text bytes | Median formatting time base → head, run 1 | Paired median reduction, runs 1 / 2 | Additional peak requested live heap base → head | Allocation/reallocation calls base → head |
|---|---|---|---|---|
| 1 / 100 | 3.37 → 0.80 µs | 76.3% / 74.1% | 6,218 → 1,024 B | 51 → 4 |
| 200 / 100 | 0.726 → 0.134 ms | 81.4% / 81.5% | 1,043,490 → 131,072 B | 8,416 → 11 |
| 5,000 / 100 | 25.70 → 3.24 ms | 87.2% / 87.4% | 27,002,722 → 4,194,304 B | 210,021 → 16 |
| 200 / 16,000 | 8.09 → 7.27 ms | 10.1% / 7.7% | 15,661,026 → 8,388,608 B | 8,422 → 17 |
The same release comparison was repeated twice with --features serde_json/preserve_order, matching workspace feature unification. The production serializer is identical; only the legacy intermediate map implementation changes.
| Messages / text bytes | Median base → head, first run | Paired median reduction, runs 1 / 2 | Additional peak requested heap base → head | Allocation/reallocation calls base → head |
|---|---|---|---|---|
| 1 / 100 | 3.46 → 0.88 µs | 74.8% / 76.2% | 4,862 → 1,024 B | 50 → 4 |
| 200 / 100 | 0.778 → 0.122 ms | 84.3% / 84.3% | 825,622 → 131,072 B | 8,017 → 11 |
| 5,000 / 100 | 22.25 → 3.12 ms | 86.1% / 86.0% | 21,562,454 → 4,194,304 B | 200,022 → 16 |
| 200 / 16,000 | 7.39 → 6.49 ms | 11.8% / 11.7% | 15,443,158 → 8,388,608 B | 8,023 → 17 |
This measures formatting only. It excludes DTO construction, IPC, stdout, process startup and the connected daemon. The input DTO is excluded from additional heap. Requested heap is not RSS; CPU time and RSS were not measured. A test allocator wrapper remains installed during timing, with accounting disabled. Long text remains dominated by escaping and output allocation. The final output String still exists.
Validated cargo test -p oxidezap-cli --all-features both with and without --features serde_json/preserve_order, scoped all-target Clippy with -D warnings including preserve_order, formatting and diff checks. The original two byte-comparison assertions failed under workspace feature unification; the follow-up commit compares parsed values and explicitly checks ok=true and data=null. The production serializer is unchanged by that follow-up. Regression tests compare parsed JSON including empty payloads/escaping/nulls, and check the existing serialization-error fallback.
Reproduce: cargo test -p oxidezap-cli --release --test json_cost -- --ignored --nocapture.
Only CLI output and its isolated test changed. No DTO/protocol, daemon, SQL, account selection, read-only, manifests, lockfiles or #265 changes. No new dependencies.
Summary by cubic
CLI JSON output now serializes a borrowed success envelope directly instead of cloning the entire payload into an intermediate serde_json::Value, cutting formatting time by up to ~87% and reducing allocations by orders of magnitude.
- Object key order follows the serializer; parsed JSON is verified with and without
preserve_order. - The null fallback on serialization failure is preserved; object key ordering is not a CLI contract.
- Adds a regression and performance benchmark test in
json_cost.rscovering timing, allocation counts, and parsed JSON equality (empty payloads, escaping, nulls, and the failure fallback).