perf(cli): avoid cloning JSON results into an intermediate tree

Merged
JL
5d ago
+187-3

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 bytesMedian formatting time base → head, run 1Paired median reduction, runs 1 / 2Additional peak requested live heap base → headAllocation/reallocation calls base → head
1 / 1003.37 → 0.80 µs76.3% / 74.1%6,218 → 1,024 B51 → 4
200 / 1000.726 → 0.134 ms81.4% / 81.5%1,043,490 → 131,072 B8,416 → 11
5,000 / 10025.70 → 3.24 ms87.2% / 87.4%27,002,722 → 4,194,304 B210,021 → 16
200 / 16,0008.09 → 7.27 ms10.1% / 7.7%15,661,026 → 8,388,608 B8,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 bytesMedian base → head, first runPaired median reduction, runs 1 / 2Additional peak requested heap base → headAllocation/reallocation calls base → head
1 / 1003.46 → 0.88 µs74.8% / 76.2%4,862 → 1,024 B50 → 4
200 / 1000.778 → 0.122 ms84.3% / 84.3%825,622 → 131,072 B8,017 → 11
5,000 / 10022.25 → 3.12 ms86.1% / 86.0%21,562,454 → 4,194,304 B200,022 → 16
200 / 16,0007.39 → 6.49 ms11.8% / 11.7%15,443,158 → 8,388,608 B8,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.rs covering timing, allocation counts, and parsed JSON equality (empty payloads, escaping, nulls, and the failure fallback).