Skip to content

Commit 977feeb

Browse files
committed
automata: make PikeVM cache initialization lazy
Prior to the advent of regex-automata, the PikeVM would decide how much space it needed at the beginning of every search. In regex-automata, we did away with that check at search time and moved it to the time at which the cache is constructed. (The inputs to the sizing are currently invariant in regex-automata, as they were in the old regex crate.) The downside of this is that we create the caches for each regex engine eagerly. So even if we never call the PikeVM (which is actually quite common, since the lazy DFA handles mostly everything), we end up paying for the memory of its cache. In many cases, this memory is likely negligible, but it can be substantial if there are a lot of capture groups, even if they aren't used. As in #1116. We fix this by just re-arranging the meta regex engine wrappers to avoid eagerly creating caches. Instead, they are only initialized when they are actually needed. This ends up making memory usage a bit less than `regex 1.7.3`. Fixes #1116
1 parent d6b3546 commit 977feeb

File tree

2 files changed

+23
-36
lines changed

2 files changed

+23
-36
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ TODO
44

55
Bug fixes:
66

7+
* [BUG #1116](https://github.com/rust-lang/regex/issues/1116):
8+
Fixes a memory usage regression for large regexes (introduced in `regex 1.9`).
79
* [BUG #1165](https://github.com/rust-lang/regex/issues/1083):
810
Fixes a panic in the lazy DFA (can only occur for especially large regexes).
911

regex-automata/src/meta/wrappers.rs

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ impl PikeVM {
5858
}
5959

6060
pub(crate) fn create_cache(&self) -> PikeVMCache {
61-
PikeVMCache::new(self)
61+
PikeVMCache::none()
6262
}
6363

6464
#[cfg_attr(feature = "perf-inline", inline(always))]
@@ -93,7 +93,7 @@ impl PikeVMEngine {
9393
cache: &mut PikeVMCache,
9494
input: &Input<'_>,
9595
) -> bool {
96-
self.0.is_match(cache.0.as_mut().unwrap(), input.clone())
96+
self.0.is_match(cache.get(&self.0), input.clone())
9797
}
9898

9999
#[cfg_attr(feature = "perf-inline", inline(always))]
@@ -103,7 +103,7 @@ impl PikeVMEngine {
103103
input: &Input<'_>,
104104
slots: &mut [Option<NonMaxUsize>],
105105
) -> Option<PatternID> {
106-
self.0.search_slots(cache.0.as_mut().unwrap(), input, slots)
106+
self.0.search_slots(cache.get(&self.0), input, slots)
107107
}
108108

109109
#[cfg_attr(feature = "perf-inline", inline(always))]
@@ -113,11 +113,7 @@ impl PikeVMEngine {
113113
input: &Input<'_>,
114114
patset: &mut PatternSet,
115115
) {
116-
self.0.which_overlapping_matches(
117-
cache.0.as_mut().unwrap(),
118-
input,
119-
patset,
120-
)
116+
self.0.which_overlapping_matches(cache.get(&self.0), input, patset)
121117
}
122118
}
123119

@@ -129,17 +125,17 @@ impl PikeVMCache {
129125
PikeVMCache(None)
130126
}
131127

132-
pub(crate) fn new(builder: &PikeVM) -> PikeVMCache {
133-
PikeVMCache(Some(builder.get().0.create_cache()))
134-
}
135-
136128
pub(crate) fn reset(&mut self, builder: &PikeVM) {
137-
self.0.as_mut().unwrap().reset(&builder.get().0);
129+
self.get(&builder.get().0).reset(&builder.get().0);
138130
}
139131

140132
pub(crate) fn memory_usage(&self) -> usize {
141133
self.0.as_ref().map_or(0, |c| c.memory_usage())
142134
}
135+
136+
fn get(&mut self, vm: &pikevm::PikeVM) -> &mut pikevm::Cache {
137+
self.0.get_or_insert_with(|| vm.create_cache())
138+
}
143139
}
144140

145141
#[derive(Debug)]
@@ -155,7 +151,7 @@ impl BoundedBacktracker {
155151
}
156152

157153
pub(crate) fn create_cache(&self) -> BoundedBacktrackerCache {
158-
BoundedBacktrackerCache::new(self)
154+
BoundedBacktrackerCache::none()
159155
}
160156

161157
#[cfg_attr(feature = "perf-inline", inline(always))]
@@ -235,9 +231,7 @@ impl BoundedBacktrackerEngine {
235231
// OK because we only permit access to this engine when we know
236232
// the haystack is short enough for the backtracker to run without
237233
// reporting an error.
238-
self.0
239-
.try_is_match(cache.0.as_mut().unwrap(), input.clone())
240-
.unwrap()
234+
self.0.try_is_match(cache.get(&self.0), input.clone()).unwrap()
241235
}
242236
#[cfg(not(feature = "nfa-backtrack"))]
243237
{
@@ -259,9 +253,7 @@ impl BoundedBacktrackerEngine {
259253
// OK because we only permit access to this engine when we know
260254
// the haystack is short enough for the backtracker to run without
261255
// reporting an error.
262-
self.0
263-
.try_search_slots(cache.0.as_mut().unwrap(), input, slots)
264-
.unwrap()
256+
self.0.try_search_slots(cache.get(&self.0), input, slots).unwrap()
265257
}
266258
#[cfg(not(feature = "nfa-backtrack"))]
267259
{
@@ -304,25 +296,10 @@ impl BoundedBacktrackerCache {
304296
}
305297
}
306298

307-
pub(crate) fn new(
308-
builder: &BoundedBacktracker,
309-
) -> BoundedBacktrackerCache {
310-
#[cfg(feature = "nfa-backtrack")]
311-
{
312-
BoundedBacktrackerCache(
313-
builder.0.as_ref().map(|e| e.0.create_cache()),
314-
)
315-
}
316-
#[cfg(not(feature = "nfa-backtrack"))]
317-
{
318-
BoundedBacktrackerCache(())
319-
}
320-
}
321-
322299
pub(crate) fn reset(&mut self, builder: &BoundedBacktracker) {
323300
#[cfg(feature = "nfa-backtrack")]
324301
if let Some(ref e) = builder.0 {
325-
self.0.as_mut().unwrap().reset(&e.0);
302+
self.get(&e.0).reset(&e.0);
326303
}
327304
}
328305

@@ -336,6 +313,14 @@ impl BoundedBacktrackerCache {
336313
0
337314
}
338315
}
316+
317+
#[cfg(feature = "nfa-backtrack")]
318+
fn get(
319+
&mut self,
320+
bb: &backtrack::BoundedBacktracker,
321+
) -> &mut backtrack::Cache {
322+
self.0.get_or_insert_with(|| bb.create_cache())
323+
}
339324
}
340325

341326
#[derive(Debug)]

0 commit comments

Comments
 (0)