Stream typed JSON lists without an adapter slice

perfloop/kubernetes · AVOIDABLE COPY

https://perfloop.ai/t/oss/case_azde8tk2g1

Verdict

VERIFIED · settled 2026-08-17

What happened: The paired measurements met the required improvement.

Hypothesis

The source constructs list := make([]runtime.Object, items.Len()) and fills it once per item. I traced its JSON consumer: json.getListMeta calls meta.ExtractList, returns that slice, streamEncodeCollections immediately passes it to encodeList, and encodeItemsObjectSlice only enumerates it sequentially. Thus the N-entry interface slice crosses the helper boundary and is not compiler-elided, but it is not reused by this JSON path. I ran `go test ./pkg/api/meta -run '^$' -bench '^BenchmarkExtractListItem$' -benchmem -count=1` from `staging/src/k8s.io/apimachinery`; its 1,000-item PointerReceiverList case measured 16,384 B/op and 1 alloc/op in ExtractList. The same fixture's value-receiver and RawExtension variants measured 1,001 allocs/op, but those fixture results do not establish production list mix or Serializer.Encode end-to-end cost. The proposed removed delta for the common pointer-item shape is the transient N-slot interface adapter (16,384 bytes in that 1,000-item check), not the item JSON payload or ownership of the original Items slice. A safe implementation must retain type/tag validation before bytes are written so invalid lists can still fall back, and must match extractList conversion semantics rather than blindly substituting EachListItem. Confirm independently with a Serializer.Encode benchmark over realistic typed lists at N=1, 100, and 1,000, checking encoded-byte equivalence plus nil/empty, RawExtension, and invalid-list cases; the expected signal is fewer bytes and allocations per list, with allocation/CPU profiles showing the removed adapter construction.

Change to test: Factor the item conversion in extractList into a non-collecting visitor usable by the JSON stream-list path, while retaining ExtractList for callers that require []runtime.Object. Have the JSON encoder emit each validated item directly, preserving nil-versus-empty Items, RawExtension Object/Raw handling, and fallback-before-write behavior.

Where it lives

perfloop/kubernetes · staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go

Evidence

Serializer.Encode streaming typed CarpList with 1 item · 10 sample pairs

metric baseline candidate paired median change confidence range required result
B/op 624 608 −2.6% (−16) −16 to −16 ≤ 0 PASSED
allocs/op 17 16 −5.9% (−1) −1 to −1 < −0.85 PASSED

Serializer.Encode streaming typed CarpList with 100 items · 10 sample pairs

metric baseline candidate paired median change confidence range required result
B/op 2407 614 −74.5% (−1793) −1793 to −1792 < −120.4 PASSED
allocs/op 17 16 −5.9% (−1) −1 to −1 ≤ 0 PASSED

Serializer.Encode streaming typed CarpList with 1000 items · 10 sample pairs

metric baseline candidate paired median change confidence range required result
B/op 17478 1098 −93.7% (−16385) −16404 to −16372 < −873.9 PASSED
allocs/op 18 17 −5.6% (−1) −1 to −1 ≤ 0 PASSED

Checks: 1 of 1 passed. Verification: no defect found.

Timeline