From 4beed4e01ad0d390f279a63bb20d61a262b9567f Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 18 Sep 2026 21:38:42 +0800 Subject: [PATCH] perf: avoid the intermediate array in from_entries (#3626) Define `from_entries` as `add(.[] | f) // {}` instead of `map(f) | add // {}`. Since `add(f)` is `reduce f as $x (null; . + $x)`, both fold the same values in the same order from the same seed, so the only difference is that the array of single-key objects built by `map` is never materialized. Measured with both the generator and the result suppressed, so that only the `from_entries` work differs: 6-15% less user time across input shapes, which is 10-24% of what `from_entries` itself costs. For inputs above roughly a thousand entries, peak RSS drops by 37%, around 80% of the memory `from_entries` adds on top of its input; a 1,000,000 entry array goes from 1064 MiB to 672 MiB. --- src/builtin.jq | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/builtin.jq b/src/builtin.jq index 134170d8..dd29a953 100644 --- a/src/builtin.jq +++ b/src/builtin.jq @@ -39,8 +39,8 @@ def recurse(f; cond): def r: ., (f | select(cond) | r); r; def recurse: recurse(.[]?); def to_entries: [keys_unsorted[] as $k | {key: $k, value: .[$k]}]; -def from_entries: map({ (.key // .Key // .name // .Name): - if has("value") then .value else .Value end }) | add // {}; +def from_entries: add(.[] | { (.key // .Key // .name // .Name): + if has("value") then .value else .Value end }) // {}; def with_entries(f): to_entries | map(f) | from_entries; def reverse: [.[range(length-1;-1;-1)]]; def indices($i): if type == "array" and ($i|type) == "array" then .[$i]