Skip to content

Commit 06f5584

Browse files
committed
automata/nfa/backtrack: fix memory usage
This fixes a memory usage regression where the backtracker would eagerly allocate its entire capacity up-front. In this case, it meant a minimum of 256KB for every regex. Prior to regex 1.9, 256KB was treated as a *maximum*, and we only allocated what we needed. We migrate that strategy to regex-automata now as well. This probably does come with a latency cost (I'll run rebar to be sure it isn't horrendous), but we definitely can't be eagerly allocating 256KB for every regex. If the latency ends up being an issue, we can investigate fixing that in other ways. Fixes #1027
1 parent c51486d commit 06f5584

File tree

1 file changed

+19
-29
lines changed

1 file changed

+19
-29
lines changed

regex-automata/src/nfa/thompson/backtrack.rs

+19-29
Original file line numberDiff line numberDiff line change
@@ -1376,7 +1376,7 @@ impl BoundedBacktracker {
13761376
for slot in slots.iter_mut() {
13771377
*slot = None;
13781378
}
1379-
cache.setup_search(&self.nfa, input)?;
1379+
cache.setup_search(&self, input)?;
13801380
if input.is_done() {
13811381
return Ok(None);
13821382
}
@@ -1750,11 +1750,11 @@ impl Cache {
17501750
/// in the BoundedBacktracker.
17511751
fn setup_search(
17521752
&mut self,
1753-
nfa: &NFA,
1753+
re: &BoundedBacktracker,
17541754
input: &Input<'_>,
17551755
) -> Result<(), MatchError> {
17561756
self.stack.clear();
1757-
self.visited.setup_search(nfa, input)?;
1757+
self.visited.setup_search(re, input)?;
17581758
Ok(())
17591759
}
17601760
}
@@ -1836,23 +1836,9 @@ impl Visited {
18361836
true
18371837
}
18381838

1839-
/// Returns the capacity of this visited set in terms of the number of bits
1840-
/// it has to track (StateID, offset) pairs.
1841-
fn capacity(&self) -> usize {
1842-
self.bitset.len() * Visited::BLOCK_SIZE
1843-
}
1844-
18451839
/// Reset this visited set to work with the given bounded backtracker.
1846-
fn reset(&mut self, re: &BoundedBacktracker) {
1847-
// The capacity given in the config is "bytes of heap memory," but the
1848-
// capacity we use here is "number of bits." So convert the capacity in
1849-
// bytes to the capacity in bits.
1850-
let capacity = 8 * re.get_config().get_visited_capacity();
1851-
let blocks = div_ceil(capacity, Visited::BLOCK_SIZE);
1852-
self.bitset.resize(blocks, 0);
1853-
// N.B. 'stride' is set in 'setup_search', since it isn't known until
1854-
// we know the length of the haystack. (That is also when we return an
1855-
// error if the haystack is too big.)
1840+
fn reset(&mut self, _: &BoundedBacktracker) {
1841+
self.bitset.truncate(0);
18561842
}
18571843

18581844
/// Setup this visited set to work for a search using the given NFA
@@ -1861,7 +1847,7 @@ impl Visited {
18611847
/// result in panics or silently incorrect search behavior.
18621848
fn setup_search(
18631849
&mut self,
1864-
nfa: &NFA,
1850+
re: &BoundedBacktracker,
18651851
input: &Input<'_>,
18661852
) -> Result<(), MatchError> {
18671853
// Our haystack length is only the length of the span of the entire
@@ -1872,19 +1858,23 @@ impl Visited {
18721858
// search loop includes the position at input.end(). (And it does this
18731859
// because matches are delayed by one byte to account for look-around.)
18741860
self.stride = haylen + 1;
1875-
let capacity = match nfa.states().len().checked_mul(self.stride) {
1876-
None => return Err(err()),
1877-
Some(capacity) => capacity,
1878-
};
1879-
if capacity > self.capacity() {
1861+
let needed_capacity =
1862+
match re.get_nfa().states().len().checked_mul(self.stride) {
1863+
None => return Err(err()),
1864+
Some(capacity) => capacity,
1865+
};
1866+
let max_capacity = 8 * re.get_config().get_visited_capacity();
1867+
if needed_capacity > max_capacity {
18801868
return Err(err());
18811869
}
1882-
// We only need to zero out our desired capacity, not our total
1883-
// capacity in this set.
1884-
let blocks = div_ceil(capacity, Visited::BLOCK_SIZE);
1885-
for block in self.bitset.iter_mut().take(blocks) {
1870+
let needed_blocks = div_ceil(needed_capacity, Visited::BLOCK_SIZE);
1871+
self.bitset.truncate(needed_blocks);
1872+
for block in self.bitset.iter_mut() {
18861873
*block = 0;
18871874
}
1875+
if needed_blocks > self.bitset.len() {
1876+
self.bitset.resize(needed_blocks, 0);
1877+
}
18881878
Ok(())
18891879
}
18901880

0 commit comments

Comments
 (0)