From 888feb71c7320a705b29fcb93d5319658b56e0b8 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sat, 7 Mar 2026 18:48:10 +0100 Subject: [PATCH 1/2] perf(decode::tree): preallocate Vec based on worst-case length We can make a (terrible) guess at how many elements our tree contains. This lets us allocate at least as many entries as we will need, allowing the function to never re-allocate. --- gix-object/src/tree/ref_iter.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/gix-object/src/tree/ref_iter.rs b/gix-object/src/tree/ref_iter.rs index 7e60d6ac831..fb11352b370 100644 --- a/gix-object/src/tree/ref_iter.rs +++ b/gix-object/src/tree/ref_iter.rs @@ -202,8 +202,16 @@ mod decode { } pub fn tree<'a, E: ParserError<&'a [u8]>>(i: &mut &'a [u8]) -> ModalResult, E> { - let mut out = Vec::new(); let mut i = &**i; + + // Calculate an estimate of the amount of entries to reduce + // the amount of allocations necessary. + // TODO(SHA256): know actual/desired length for reduced overallocation + const HASH_LEN_FIXME: usize = 20; + let lower_bound_single_entry = 2 + HASH_LEN_FIXME; // 2 = space + trailing zero + let upper_bound_entries = i.len() / lower_bound_single_entry; + let mut out = Vec::with_capacity(upper_bound_entries); + while !i.is_empty() { let Some((rest, entry)) = fast_entry(i) else { #[allow(clippy::unit_arg)] From 16bb9052d5e2b82c1bb380d601b24bf76b7ed5bd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 22 Mar 2026 15:28:59 +0800 Subject: [PATCH 2/2] Make sure we don't pre-allocate beyond some sane amount This should already have the desired effect. --- gix-object/src/tree/ref_iter.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/gix-object/src/tree/ref_iter.rs b/gix-object/src/tree/ref_iter.rs index fb11352b370..f366f7c0324 100644 --- a/gix-object/src/tree/ref_iter.rs +++ b/gix-object/src/tree/ref_iter.rs @@ -206,11 +206,16 @@ mod decode { // Calculate an estimate of the amount of entries to reduce // the amount of allocations necessary. + // Note that this assumes that we want speed over fitting Vecs, this is a trade-off. // TODO(SHA256): know actual/desired length for reduced overallocation const HASH_LEN_FIXME: usize = 20; - let lower_bound_single_entry = 2 + HASH_LEN_FIXME; // 2 = space + trailing zero - let upper_bound_entries = i.len() / lower_bound_single_entry; - let mut out = Vec::with_capacity(upper_bound_entries); + const AVERAGE_FILENAME_LEN: usize = 24; + const AVERAGE_MODE_LEN: usize = 6; + const ENTRY_DELIMITER_LEN: usize = 2; // space + trailing zero + const AVERAGE_TREE_ENTRIES: usize = 16 * 2; // prevent overallocation beyond what's meaningful or what could be dangerous + let average_entry_len = ENTRY_DELIMITER_LEN + HASH_LEN_FIXME + AVERAGE_MODE_LEN + AVERAGE_FILENAME_LEN; + let upper_bound = i.len() / average_entry_len; + let mut out = Vec::with_capacity(upper_bound.min(AVERAGE_TREE_ENTRIES)); while !i.is_empty() { let Some((rest, entry)) = fast_entry(i) else {