Skip to content
Draft
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
1 change: 1 addition & 0 deletions changelog.d/9918-regex-cache-eviction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Keep compiled programs for recorded regular-expression literal sites across bounded cache eviction, and replace whole-cache overflow clears with one-entry eviction.
10 changes: 2 additions & 8 deletions crates/perry-runtime/src/gc/tests/copying/survival_and_malloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,6 @@ fn test_movable_regexp_evacuation_migrates_all_address_owned_state() {
let old_addr = re as usize;
assert!(crate::arena::pointer_in_nursery(old_addr));
assert!(crate::regex::test_regex_pointer_entry_exists(old_addr));
assert!(crate::regex::test_regex_source_entry_exists(old_addr));

crate::object::exotic_expando::test_seed_exotic_expando_entry(
old_addr,
Expand All @@ -942,8 +941,6 @@ fn test_movable_regexp_evacuation_migrates_all_address_owned_state() {

assert!(crate::regex::test_regex_pointer_entry_exists(new_addr));
assert!(!crate::regex::test_regex_pointer_entry_exists(old_addr));
assert!(crate::regex::test_regex_source_entry_exists(new_addr));
assert!(!crate::regex::test_regex_source_entry_exists(old_addr));
assert!(crate::object::exotic_expando::test_exotic_expando_entry_exists(new_addr));
assert!(!crate::object::exotic_expando::test_exotic_expando_entry_exists(old_addr));

Expand Down Expand Up @@ -1037,9 +1034,8 @@ fn nursery_regexp_that_dies_young_is_finalized_by_the_copied_minor() {
"the header must be nursery-allocated"
);
assert!(crate::regex::test_regex_pointer_entry_exists(dead_addr));
assert!(crate::regex::test_regex_source_entry_exists(dead_addr));
// Both headers share one program through the site cache.
let count_before = crate::regex::test_regexp_std_program_strong_count(live);
let count_before = crate::regex::test_regexp_program_set_strong_count(live);
assert!(count_before >= 2);

// Only `live` is rooted; `dead` is garbage.
Expand All @@ -1051,15 +1047,13 @@ fn nursery_regexp_that_dies_young_is_finalized_by_the_copied_minor() {
assert_ne!(live_new, live_addr, "the rooted RegExp must be evacuated");
assert!(crate::regex::regex_header_has_magic(live_new as *const _));
assert!(crate::regex::test_regex_pointer_entry_exists(live_new));
assert!(crate::regex::test_regex_source_entry_exists(live_new));

assert!(
!crate::regex::test_regex_pointer_entry_exists(dead_addr),
"a nursery RegExp that died must be removed from REGEX_POINTERS by the copied minor"
);
assert!(!crate::regex::test_regex_source_entry_exists(dead_addr));
assert_eq!(
crate::regex::test_regexp_std_program_strong_count(live_new as *const _),
crate::regex::test_regexp_program_set_strong_count(live_new as *const _),
count_before - 1,
"the dead header's Arc clone of the shared program must have been dropped"
);
Expand Down
6 changes: 3 additions & 3 deletions crates/perry-runtime/src/gc/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,9 @@ pub(crate) enum GcMoveHookKind {
/// live on the Error's traced `ObjectMeta` edge and need no side-table
/// rekeying.
ErrorSideTables,
/// Rekey RegExp identity/source registries plus its exotic expando owner
/// entry. `GC_TYPE_REGEXP` is movable, and all three tables use the
/// payload address as their key.
/// Rekey the RegExp identity registry plus its exotic expando owner entry.
/// `GC_TYPE_REGEXP` is movable, and both tables use the payload address as
/// their key.
RegExpSideTables,
}

Expand Down
63 changes: 57 additions & 6 deletions crates/perry-runtime/src/hot_diag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ pub struct RegexDiag {
pub compiles_std: u64,
pub compiles_fancy: u64,
pub compiles_repeat: u64,
/// One-entry evictions after a regex cache reaches its bound. The former
/// wholesale-clear counter remains as a zeroed regression control.
pub cache_evictions: u64,
pub cache_clears: u64,
/// `lazy::build_and_install_programs` runs (one per header that is
/// executed at least once).
Expand Down Expand Up @@ -169,17 +172,31 @@ pub struct RegexDiag {
/// or missed: this is the `memcmp` volume alone, which is what a 12 KB
/// emoji pattern makes expensive and a 60-byte one does not.
pub new_site_verify_bytes: u64,
/// Address-keyed side-table inserts performed per construction
/// (`REGEX_POINTERS` and `REGEX_SOURCE_TABLE`) — two per header, each a
/// `PtrHasher` hash plus a hashbrown insert, mirrored by two removals at
/// death and two rekeys per evacuation.
/// Address-keyed side-table inserts performed per construction. This was
/// two (`REGEX_POINTERS` plus the source table) before the header's string
/// slots became traced edges; only `REGEX_POINTERS` remains.
pub new_side_table_inserts: u64,
/// Split of the above by table. The source counters are retained as zeroed
/// before/after controls for the #9908 measurement; `REGEX_POINTERS` is
/// still the registry the copied-minor finaliser enumerates.
pub pointer_table_inserts: u64,
pub source_table_inserts: u64,
/// The death side. `source_table_removals` is the zeroed after-control;
/// `regex_header_clear_dead_for_gc` now removes only `REGEX_POINTERS`.
pub pointer_table_removals: u64,
pub source_table_removals: u64,
/// Evacuation rekeys of the remaining pointer registry.
pub side_table_rekeys: u64,
/// Constructions answered from the LITERAL-SITE table — identity by the
/// compiler-emitted site global's address, so neither the pattern's
/// fingerprint nor its byte compare ran. `site_hit` counts the
/// CONTENT-keyed cache; a site hit never reaches it, so the two are
/// disjoint and `site_key_hit + site_hit <= new`.
pub new_site_key_hit: u64,
#[cfg(test)]
test_program_builds: u64,
#[cfg(test)]
test_cache_evictions: u64,
per_pattern: HashMap<usize, PatStat>,
}

Expand Down Expand Up @@ -242,6 +259,33 @@ pub fn regex_with(f: impl FnOnce(&mut RegexDiag)) {
});
}

#[cfg(test)]
pub(crate) fn test_reset_regex_builds_and_evictions() {
REGEX_DIAG.with(|diag| {
let mut diag = diag.borrow_mut();
diag.test_program_builds = 0;
diag.test_cache_evictions = 0;
});
}

#[cfg(test)]
pub(crate) fn test_note_regex_program_build() {
REGEX_DIAG.with(|diag| diag.borrow_mut().test_program_builds += 1);
}

#[cfg(test)]
pub(crate) fn test_note_regex_cache_eviction() {
REGEX_DIAG.with(|diag| diag.borrow_mut().test_cache_evictions += 1);
}

#[cfg(test)]
pub(crate) fn test_regex_builds_and_evictions() -> (u64, u64) {
REGEX_DIAG.with(|diag| {
let diag = diag.borrow();
(diag.test_program_builds, diag.test_cache_evictions)
})
}

impl RegexDiag {
fn pat(&mut self, pattern_addr: usize, pattern: &[u8], flags: &str) -> &mut PatStat {
let entry = self.per_pattern.entry(pattern_addr).or_default();
Expand Down Expand Up @@ -321,12 +365,13 @@ impl RegexDiag {
let _ = writeln!(
out,
"[regex-diag] t={secs:.1}s new={} validated_hit={} site_hit={} pattern_bytes={} \
compiles std={} fancy={} repeat={} cache_clears={} lazy_builds={} lazy_cache_hits={} \
compiles std={} fancy={} repeat={} cache_clears={} evictions={} lazy_builds={} lazy_cache_hits={} \
exec={} exec_matched={} capture_slots={} capture_bytes={} test={} test_global={} \
match={} replace={} replace_matches={} split={} flags_alloc={} \
desc_regexp_probes={} desc_regexp_meta_negative={} \
barrier_taken={} barrier_gated={} header_bytes={} site_verify_bytes={} \
side_table_inserts={} site_key_hit={}",
side_table_inserts={} site_key_hit={} ptr_ins={} src_ins={} \
ptr_rm={} src_rm={} rekeys={}",
self.new_calls,
self.new_validated_hit,
self.new_site_hit,
Expand All @@ -335,6 +380,7 @@ impl RegexDiag {
self.compiles_fancy,
self.compiles_repeat,
self.cache_clears,
self.cache_evictions,
self.lazy_builds,
self.lazy_cache_hits,
self.exec_calls,
Expand All @@ -356,6 +402,11 @@ impl RegexDiag {
self.new_site_verify_bytes,
self.new_side_table_inserts,
self.new_site_key_hit,
self.pointer_table_inserts,
self.source_table_inserts,
self.pointer_table_removals,
self.source_table_removals,
self.side_table_rekeys,
);
// Merge by content (prefix, len, flags): distinct literal sites with
// the same pattern are one row.
Expand Down
Loading