Skip to content

Commit 606a261

Browse files
authored
Merge pull request #278 from bgpkit/dev/memory-optimizations
Memory usage optimizations - 61% reduction
2 parents fd11e4e + 6f8ad2f commit 606a261

4 files changed

Lines changed: 325 additions & 100 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ All notable changes to this project will be documented in this file.
5454

5555
### Performance improvements
5656

57+
* **Memory usage reduction (61% improvement)**: Optimized memory allocation patterns resulting in significant peak memory reduction:
58+
- **NLRI Add-Path clone-free parsing**: Replaced input buffer cloning with speculative byte-slice parsing. The new `try_parse_prefix` function parses from a byte slice without consuming, returning `(prefix, bytes_consumed)`. The `read_nlri_prefix` method is now a thin wrapper that calls `try_parse_prefix` and advances the cursor. This eliminates the expensive `input.clone()` operation while maintaining correct Add-Path heuristic retry behavior.
59+
- **Attribute vector pre-allocation**: Estimate capacity from data size (`remaining / 3` bytes per attribute) instead of fixed 20, reducing reallocations for BGP messages with many attributes.
60+
- **RIS Live vector pre-allocation**: Calculate capacity from announcements + withdrawals counts before allocation, preventing growth reallocations.
61+
- **Measured improvement**: Peak memory reduced from 2,037 MB to 789 MB (61.3% reduction) when parsing full BGP table dump (RouteViews LINX RIB, 151MB compressed).
62+
- **Code quality**: Single implementation of prefix parsing logic in `try_parse_prefix`, eliminating duplication with `read_nlri_prefix`.
63+
5764
* Use zerocopy for MRT header parsing
5865
- Replaced manual byte parsing with zerocopy's `FromBytes` trait for `RawMrtCommonHeader` and `RawMrtEtCommonHeader`
5966
- Reduces bounds checking overhead by using compile-time verified struct layouts

src/parser/bgp/attributes/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,10 @@ pub fn parse_attributes(
180180
safi: Option<Safi>,
181181
prefixes: Option<&[NetworkPrefix]>,
182182
) -> Result<Attributes, ParserError> {
183-
let mut attributes: Vec<Attribute> = Vec::with_capacity(20);
183+
// Estimate capacity from data size: each attribute is at least 3 bytes
184+
// (flag + type + length). Cap at 256 to avoid over-allocation for corrupted data.
185+
let estimated_attrs = (data.remaining() / 3).min(256);
186+
let mut attributes: Vec<Attribute> = Vec::with_capacity(estimated_attrs.max(8));
184187
let mut validation_warnings: Vec<BgpValidationWarning> = Vec::new();
185188
// boolean flags for seen attributes - small dataset in hot loop.
186189
let mut seen_attributes: [bool; 256] = [false; 256];

src/parser/rislive/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,14 @@ pub fn parse_ris_live_message(msg_str: &str) -> Result<Vec<BgpElem>, ParserRisli
103103
announcements,
104104
withdrawals,
105105
} => {
106-
let mut elems: Vec<BgpElem> = vec![];
106+
// Pre-allocate capacity based on announcements + withdrawals
107+
let announce_count: usize = announcements
108+
.as_ref()
109+
.map(|a| a.iter().map(|ann| ann.prefixes.len()).sum())
110+
.unwrap_or(0);
111+
let withdraw_count: usize = withdrawals.as_ref().map(|w| w.len()).unwrap_or(0);
112+
let mut elems: Vec<BgpElem> =
113+
Vec::with_capacity(announce_count + withdraw_count);
107114

108115
// parse community
109116
let communities = community.map(|values| {

0 commit comments

Comments
 (0)