Skip to content

Commit 2e02f33

Browse files
morealclaude
andcommitted
Avoid into_owned() tree rebuild in Decode for Vec<u8>
Parameterize decode_impl with const generic BORROW to control whether leaf values (Binary/Text) use Cow::Borrowed or Cow::Owned. When BORROW=false, owned values are produced directly during parsing, eliminating the post-decode into_owned() walk that was rebuilding every BTreeMap and Vec container. This fixes the 10-20% decode_scalar regression on small/medium files introduced by the Cow migration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0219b83 commit 2e02f33

1 file changed

Lines changed: 108 additions & 48 deletions

File tree

src/codec/decode.rs

Lines changed: 108 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ impl Expect<u8> for u8 {
110110
}
111111
}
112112

113-
fn decode_impl<'a>(
113+
/// When `BORROW` is true, Binary/Text values borrow from the input slice (zero-copy).
114+
/// When `BORROW` is false, Binary/Text values are owned copies (for `Decode for Vec<u8>`).
115+
fn decode_impl<'a, const BORROW: bool>(
114116
vector: &'a [u8],
115117
start: usize,
116118
) -> Result<(BencodexValue<'a>, usize), DecodeError> {
@@ -119,11 +121,11 @@ fn decode_impl<'a>(
119121
}
120122

121123
match vector[start] {
122-
b'd' => decode_dict_impl(vector, start),
123-
b'l' => decode_list_impl(vector, start),
124-
b'u' => decode_unicode_string_impl(vector, start),
124+
b'd' => decode_dict_impl::<BORROW>(vector, start),
125+
b'l' => decode_list_impl::<BORROW>(vector, start),
126+
b'u' => decode_unicode_string_impl::<BORROW>(vector, start),
125127
b'i' => decode_number_impl(vector, start),
126-
b'0'..=b'9' => decode_byte_string_impl(vector, start),
128+
b'0'..=b'9' => decode_byte_string_impl::<BORROW>(vector, start),
127129
b't' => Ok((BencodexValue::Boolean(true), 1)),
128130
b'f' => Ok((BencodexValue::Boolean(false), 1)),
129131
b'n' => Ok((BencodexValue::Null, 1)),
@@ -134,8 +136,26 @@ fn decode_impl<'a>(
134136
}
135137
}
136138

139+
#[inline(always)]
140+
fn cow_binary<'a, const BORROW: bool>(data: &'a [u8]) -> Cow<'a, [u8]> {
141+
if BORROW {
142+
Cow::Borrowed(data)
143+
} else {
144+
Cow::Owned(data.to_vec())
145+
}
146+
}
147+
148+
#[inline(always)]
149+
fn cow_text<'a, const BORROW: bool>(text: &'a str) -> Cow<'a, str> {
150+
if BORROW {
151+
Cow::Borrowed(text)
152+
} else {
153+
Cow::Owned(text.to_string())
154+
}
155+
}
156+
137157
// start must be on 'd'
138-
fn decode_dict_impl<'a>(
158+
fn decode_dict_impl<'a, const BORROW: bool>(
139159
vector: &'a [u8],
140160
start: usize,
141161
) -> Result<(BencodexValue<'a>, usize), DecodeError> {
@@ -148,15 +168,15 @@ fn decode_dict_impl<'a>(
148168
let mut index = start + tsize;
149169
let mut map = BTreeMap::new();
150170
while vector.get(index).should_not_be_none()? != b'e' {
151-
let (value, size) = decode_impl(vector, index)?;
171+
let (value, size) = decode_impl::<BORROW>(vector, index)?;
152172
let key = match value {
153173
BencodexValue::Text(s) => BencodexKey::Text(s),
154174
BencodexValue::Binary(b) => BencodexKey::Binary(b),
155175
_ => return Err(DecodeError::InvalidBencodexValueError),
156176
};
157177
tsize += size;
158178
index = start + tsize;
159-
let (value, size) = decode_impl(vector, index)?;
179+
let (value, size) = decode_impl::<BORROW>(vector, index)?;
160180

161181
match map.insert(key, value) {
162182
None => (),
@@ -176,7 +196,7 @@ fn decode_dict_impl<'a>(
176196
}
177197

178198
// start must be on 'l'
179-
fn decode_list_impl<'a>(
199+
fn decode_list_impl<'a, const BORROW: bool>(
180200
vector: &'a [u8],
181201
start: usize,
182202
) -> Result<(BencodexValue<'a>, usize), DecodeError> {
@@ -189,7 +209,7 @@ fn decode_list_impl<'a>(
189209
let mut list = Vec::new();
190210
let mut index = start + tsize;
191211
while vector.get(index).should_not_be_none()? != b'e' {
192-
let (value, size) = decode_impl(vector, index)?;
212+
let (value, size) = decode_impl::<BORROW>(vector, index)?;
193213
list.push(value);
194214
tsize += size;
195215
index = start + tsize
@@ -205,7 +225,7 @@ fn decode_list_impl<'a>(
205225
Ok((BencodexValue::List(list), tsize))
206226
}
207227

208-
fn decode_byte_string_impl<'a>(
228+
fn decode_byte_string_impl<'a, const BORROW: bool>(
209229
vector: &'a [u8],
210230
start: usize,
211231
) -> Result<(BencodexValue<'a>, usize), DecodeError> {
@@ -226,15 +246,15 @@ fn decode_byte_string_impl<'a>(
226246
return Err(DecodeError::InvalidBencodexValueError);
227247
}
228248
Ok((
229-
BencodexValue::Binary(Cow::Borrowed(
249+
BencodexValue::Binary(cow_binary::<BORROW>(
230250
&vector[start + tsize..start + tsize + length],
231251
)),
232252
tsize + length,
233253
))
234254
}
235255

236256
// start must be on 'u'
237-
fn decode_unicode_string_impl<'a>(
257+
fn decode_unicode_string_impl<'a, const BORROW: bool>(
238258
vector: &'a [u8],
239259
start: usize,
240260
) -> Result<(BencodexValue<'a>, usize), DecodeError> {
@@ -273,7 +293,7 @@ fn decode_unicode_string_impl<'a>(
273293
Err(_) => return Err(DecodeError::InvalidBencodexValueError),
274294
};
275295
tsize += length;
276-
Ok((BencodexValue::Text(Cow::Borrowed(text)), tsize))
296+
Ok((BencodexValue::Text(cow_text::<BORROW>(text)), tsize))
277297
}
278298

279299
// start must be on 'i'
@@ -386,7 +406,7 @@ fn read_number(s: &[u8]) -> Option<(BigInt, usize)> {
386406
/// assert_eq!(value, BencodexValue::Text("hello".into()));
387407
/// ```
388408
pub fn decode_borrowed(input: &[u8]) -> Result<BencodexValue<'_>, DecodeError> {
389-
Ok(decode_impl(input, 0)?.0)
409+
Ok(decode_impl::<true>(input, 0)?.0)
390410
}
391411

392412
impl Decode for Vec<u8> {
@@ -400,7 +420,11 @@ impl Decode for Vec<u8> {
400420
/// assert_eq!(dictionary, BencodexValue::Dictionary(BTreeMap::new()));
401421
/// ```
402422
fn decode(self) -> Result<BencodexValue<'static>, DecodeError> {
403-
Ok(decode_impl(&self, 0)?.0.into_owned())
423+
let value = decode_impl::<false>(&self, 0)?.0;
424+
// SAFETY: With BORROW=false, all Cow values in the tree are Cow::Owned,
425+
// so no references with the local lifetime exist. The lifetime parameter
426+
// is purely phantom, making the transmute to 'static sound.
427+
Ok(unsafe { core::mem::transmute::<BencodexValue<'_>, BencodexValue<'static>>(value) })
404428
}
405429

406430
#[cfg(feature = "simd")]
@@ -417,9 +441,9 @@ mod tests {
417441
#[test]
418442
fn should_return_error_with_overflowed_start() {
419443
let expected_error = DecodeError::InvalidBencodexValueError;
420-
assert_eq!(expected_error, decode_impl(&[], 1).unwrap_err());
421-
assert_eq!(expected_error, decode_impl(b"12", 2).unwrap_err());
422-
assert_eq!(expected_error, decode_impl(b"12", 20).unwrap_err());
444+
assert_eq!(expected_error, decode_impl::<true>(&[], 1).unwrap_err());
445+
assert_eq!(expected_error, decode_impl::<true>(b"12", 2).unwrap_err());
446+
assert_eq!(expected_error, decode_impl::<true>(b"12", 20).unwrap_err());
423447
}
424448

425449
#[test]
@@ -429,14 +453,14 @@ mod tests {
429453
token: b'x',
430454
point: 0,
431455
},
432-
decode_impl(b"x", 0).unwrap_err()
456+
decode_impl::<true>(b"x", 0).unwrap_err()
433457
);
434458
assert_eq!(
435459
DecodeError::UnexpectedTokenError {
436460
token: b'k',
437461
point: 4,
438462
},
439-
decode_impl(b"xyzok", 4).unwrap_err()
463+
decode_impl::<true>(b"xyzok", 4).unwrap_err()
440464
);
441465
}
442466
}
@@ -447,24 +471,48 @@ mod tests {
447471
#[test]
448472
fn should_return_error_with_insufficient_length_source() {
449473
let expected_error = DecodeError::InvalidBencodexValueError;
450-
assert_eq!(expected_error, decode_dict_impl(b"d", 0).unwrap_err());
451-
assert_eq!(expected_error, decode_dict_impl(b"d", 2).unwrap_err());
452-
assert_eq!(expected_error, decode_dict_impl(&[], 0).unwrap_err());
474+
assert_eq!(
475+
expected_error,
476+
decode_dict_impl::<true>(b"d", 0).unwrap_err()
477+
);
478+
assert_eq!(
479+
expected_error,
480+
decode_dict_impl::<true>(b"d", 2).unwrap_err()
481+
);
482+
assert_eq!(
483+
expected_error,
484+
decode_dict_impl::<true>(&[], 0).unwrap_err()
485+
);
453486
}
454487

455488
#[test]
456489
fn should_return_error_with_source_having_incorrect_key() {
457490
let expected_error = DecodeError::InvalidBencodexValueError;
458491
// { 0: null }
459-
assert_eq!(expected_error, decode_dict_impl(b"di0ene", 0).unwrap_err());
492+
assert_eq!(
493+
expected_error,
494+
decode_dict_impl::<true>(b"di0ene", 0).unwrap_err()
495+
);
460496
// { null: null }
461-
assert_eq!(expected_error, decode_dict_impl(b"dnne", 0).unwrap_err());
497+
assert_eq!(
498+
expected_error,
499+
decode_dict_impl::<true>(b"dnne", 0).unwrap_err()
500+
);
462501
// { list: null }
463-
assert_eq!(expected_error, decode_dict_impl(b"dlene", 0).unwrap_err());
502+
assert_eq!(
503+
expected_error,
504+
decode_dict_impl::<true>(b"dlene", 0).unwrap_err()
505+
);
464506
// { dictionary: null }
465-
assert_eq!(expected_error, decode_dict_impl(b"ddene", 0).unwrap_err());
507+
assert_eq!(
508+
expected_error,
509+
decode_dict_impl::<true>(b"ddene", 0).unwrap_err()
510+
);
466511
// { boolean: null }
467-
assert_eq!(expected_error, decode_dict_impl(b"dtene", 0).unwrap_err());
512+
assert_eq!(
513+
expected_error,
514+
decode_dict_impl::<true>(b"dtene", 0).unwrap_err()
515+
);
468516
}
469517

470518
#[test]
@@ -474,14 +522,14 @@ mod tests {
474522
token: b'k',
475523
point: 1,
476524
},
477-
decode_dict_impl(b"dkne", 0).unwrap_err()
525+
decode_dict_impl::<true>(b"dkne", 0).unwrap_err()
478526
);
479527
assert_eq!(
480528
DecodeError::UnexpectedTokenError {
481529
token: b'k',
482530
point: 4,
483531
},
484-
decode_dict_impl(b"d1:ake", 0).unwrap_err()
532+
decode_dict_impl::<true>(b"d1:ake", 0).unwrap_err()
485533
);
486534
}
487535
}
@@ -492,9 +540,18 @@ mod tests {
492540
#[test]
493541
fn should_return_error_with_insufficient_length_source() {
494542
let expected_error = DecodeError::InvalidBencodexValueError;
495-
assert_eq!(expected_error, decode_list_impl(b"l", 0).unwrap_err());
496-
assert_eq!(expected_error, decode_list_impl(b"l", 2).unwrap_err());
497-
assert_eq!(expected_error, decode_list_impl(&[], 0).unwrap_err());
543+
assert_eq!(
544+
expected_error,
545+
decode_list_impl::<true>(b"l", 0).unwrap_err()
546+
);
547+
assert_eq!(
548+
expected_error,
549+
decode_list_impl::<true>(b"l", 2).unwrap_err()
550+
);
551+
assert_eq!(
552+
expected_error,
553+
decode_list_impl::<true>(&[], 0).unwrap_err()
554+
);
498555
}
499556

500557
#[test]
@@ -504,7 +561,7 @@ mod tests {
504561
token: b'k',
505562
point: 1,
506563
},
507-
decode_list_impl(b"lke", 0).unwrap_err()
564+
decode_list_impl::<true>(b"lke", 0).unwrap_err()
508565
);
509566
}
510567
}
@@ -517,17 +574,20 @@ mod tests {
517574
let expected_error = DecodeError::InvalidBencodexValueError;
518575
assert_eq!(
519576
expected_error,
520-
decode_byte_string_impl(b"1", 0).unwrap_err()
577+
decode_byte_string_impl::<true>(b"1", 0).unwrap_err()
578+
);
579+
assert_eq!(
580+
expected_error,
581+
decode_byte_string_impl::<true>(b"1:", 0).unwrap_err()
521582
);
522583
assert_eq!(
523584
expected_error,
524-
decode_byte_string_impl(b"1:", 0).unwrap_err()
585+
decode_byte_string_impl::<true>(b"2:a", 0).unwrap_err()
525586
);
526587
assert_eq!(
527588
expected_error,
528-
decode_byte_string_impl(b"2:a", 0).unwrap_err()
589+
decode_byte_string_impl::<true>(&[], 0).unwrap_err()
529590
);
530-
assert_eq!(expected_error, decode_byte_string_impl(&[], 0).unwrap_err());
531591
}
532592

533593
#[test]
@@ -537,7 +597,7 @@ mod tests {
537597
token: b'k',
538598
point: 1,
539599
},
540-
decode_byte_string_impl(b"1ka", 0).unwrap_err()
600+
decode_byte_string_impl::<true>(b"1ka", 0).unwrap_err()
541601
);
542602
}
543603
}
@@ -550,26 +610,26 @@ mod tests {
550610
let expected_error = DecodeError::InvalidBencodexValueError;
551611
assert_eq!(
552612
expected_error,
553-
decode_unicode_string_impl(b"u", 0).unwrap_err()
613+
decode_unicode_string_impl::<true>(b"u", 0).unwrap_err()
554614
);
555615
assert_eq!(
556616
expected_error,
557-
decode_unicode_string_impl(b"u1", 0).unwrap_err()
617+
decode_unicode_string_impl::<true>(b"u1", 0).unwrap_err()
558618
);
559619
assert_eq!(
560620
expected_error,
561-
decode_unicode_string_impl(b"u2:a", 0).unwrap_err()
621+
decode_unicode_string_impl::<true>(b"u2:a", 0).unwrap_err()
562622
);
563623
assert_eq!(
564624
DecodeError::UnexpectedTokenError {
565625
token: b'k',
566626
point: 1,
567627
},
568-
decode_unicode_string_impl(b"uk", 0).unwrap_err()
628+
decode_unicode_string_impl::<true>(b"uk", 0).unwrap_err()
569629
);
570630
assert_eq!(
571631
expected_error,
572-
decode_unicode_string_impl(&[], 0).unwrap_err()
632+
decode_unicode_string_impl::<true>(&[], 0).unwrap_err()
573633
);
574634
}
575635

@@ -580,7 +640,7 @@ mod tests {
580640
token: b'k',
581641
point: 2
582642
},
583-
decode_unicode_string_impl(b"u1ka", 0).unwrap_err()
643+
decode_unicode_string_impl::<true>(b"u1ka", 0).unwrap_err()
584644
);
585645
}
586646

@@ -591,15 +651,15 @@ mod tests {
591651
token: b'-',
592652
point: 1,
593653
},
594-
decode_unicode_string_impl(b"u-1:a", 0).unwrap_err()
654+
decode_unicode_string_impl::<true>(b"u-1:a", 0).unwrap_err()
595655
);
596656
}
597657

598658
#[test]
599659
fn should_return_error_with_invalid_source_having_invalid_unicode_string() {
600660
assert_eq!(
601661
DecodeError::InvalidBencodexValueError,
602-
decode_unicode_string_impl(&[b'u', b'1', b':', 0x90], 0).unwrap_err()
662+
decode_unicode_string_impl::<true>(&[b'u', b'1', b':', 0x90], 0).unwrap_err()
603663
);
604664
}
605665
}

0 commit comments

Comments
 (0)