Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ chrono = "0.4"
regex = "1.12"
aho-corasick = "1.1"
# The single regular-expression engine, through crates/perry-perex.
perex = "0.1.7"
perex = "0.1.9"
hex = "0.4"
tempfile = "3"
itoa = "1.0"
Expand Down
3 changes: 3 additions & 0 deletions changelog.d/10580-search-run-one-entry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Faster

- Regular expression calls spend 305 to 332 fewer instructions each, 4.3% to 12.5% depending on the pattern. A `test` or `exec` call used to acquire the engine's program and subject views twice — once to learn the shape of the search and again to run it — and move the search between them; it now does both in one entry, and a search that its first quantum decides is never built as a resumable one at all (#10166).
91 changes: 79 additions & 12 deletions crates/perry-runtime/src/regex/perex_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use perex::binding::{
};
use perex::compiler::{self, CompileError, Node, Range};
use perex::executor::{
ExecError, Frame, Progress, Scratch, ScratchOwner, ScratchRequirements, Search, SearchError,
Undo,
ExecError, Frame, Progress, Run, Scratch, ScratchOwner, ScratchRequirements, Search,
SearchError, Undo,
};
use perex::input::Position;
use perex::span::Span;
Expand Down Expand Up @@ -382,11 +382,46 @@ fn find_near_lent<'mem, S: ImmutableSubject<Error = OwnerError>>(
undo: &mut cell.undo[..],
};
poll_on_stride(poll)?;
let mut search = match near {
Some(near) => Search::new_near(resources, start, near, scratch, *budget),
None => Search::new(resources, start, scratch, *budget),
}
.map_err(search_error)?;
// Both views are acquired once for the quantum that decides nearly
// every per-call search; a `Search` is built and moved only if this
// one pauses or asks for more scratch.
// On an error that is not a capacity request -- WorkLimit,
// InvalidProgram, ChangedResources, Cancelled -- `run` returns Err and
// drops the scratch and the remaining budget, where `new` + `advance`
// left a Search to read `remaining_work()` from. So `*budget` keeps its
// entry value and under-counts what the failed call spent. WORK is
// usize::MAX on every path here, so nothing observes it today; if a
// finite execution budget is ever reintroduced (see #10164/#10165),
// this stops being free and wants the engine's failure arm to report
// remaining work.
let mut search = match Search::run(resources, start, near, scratch, *budget, quantum)
.map_err(search_error)?
{
Run::Finished(mut finished) => {
*budget = Budget::new(finished.remaining_work());
let position = finished.position();
if !finished.matched() {
return Ok(Lent::Done(None, position));
}
let full = finished
.capture(0)
.map_err(EngineError::Execution)?
.ok_or(EngineError::Execution(ExecError::InvalidProgram))?;
let captures = match mode {
CaptureMode::Full => None,
CaptureMode::All => {
poll()?;
let mut output = Slots::new(memory, finished.capture_count())?;
finished
.copy_captures(&mut output)
.map_err(EngineError::Execution)?;
Some(output)
}
};
return Ok(Lent::Done(Some(Match { full, captures }), position));
}
Run::Paused(search) => search,
};
loop {
let result = search.advance(quantum);
*budget = Budget::new(search.remaining_work());
Expand Down Expand Up @@ -482,11 +517,43 @@ pub(crate) fn find_near<'mem, S: ImmutableSubject<Error = OwnerError>>(

poll()?;
let buffers = MatchBuffers::new(memory, size)?;
let mut search = match near {
Some(near) => Search::new_near(&resources, start, near, buffers, *budget),
None => Search::new(&resources, start, buffers, *budget),
}
.map_err(search_error)?;
// On an error that is not a capacity request -- WorkLimit,
// InvalidProgram, ChangedResources, Cancelled -- `run` returns Err and
// drops the scratch and the remaining budget, where `new` + `advance`
// left a Search to read `remaining_work()` from. So `*budget` keeps its
// entry value and under-counts what the failed call spent. WORK is
// usize::MAX on every path here, so nothing observes it today; if a
// finite execution budget is ever reintroduced (see #10164/#10165),
// this stops being free and wants the engine's failure arm to report
// remaining work.
let mut search = match Search::run(&resources, start, near, buffers, *budget, quantum)
.map_err(search_error)?
{
Run::Finished(mut finished) => {
*budget = Budget::new(finished.remaining_work());
let position = finished.position();
if !finished.matched() {
return Ok((None, position));
}
let full = finished
.capture(0)
.map_err(EngineError::Execution)?
.ok_or(EngineError::Execution(ExecError::InvalidProgram))?;
let captures = match mode {
CaptureMode::Full => None,
CaptureMode::All => {
poll()?;
let mut output = Slots::new(memory, finished.capture_count())?;
finished
.copy_captures(&mut output)
.map_err(EngineError::Execution)?;
Some(output)
}
};
return Ok((Some(Match { full, captures }), position));
}
Run::Paused(search) => search,
};
loop {
let result = search.advance(quantum);
// Preserve consumed work even when the following poll cancels/throws,
Expand Down
Loading