From 91692846909ed191addb7ec1c34aad11392ab88b Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 18 Jul 2026 15:03:55 +0900 Subject: perf(regex): eliminate per-call clone overhead in preg_* dispatch regex::Regex::clone() does not share the underlying meta engine's search-cache pool, so every fresh clone pays a ~10us warmup cost on its first use. Two changes together eliminate this across nearly all preg_* call sites: - A php_regex! macro resolves PHP-style patterns to a per-call-site &'static regex::Regex (via regex-macro's LazyLock), applied at the majority of call sites throughout the codebase. - Call sites still passing dynamic pattern strings go through PATTERN_CACHE, which now stores Arc<(Regex, bool)> and hands out Arc::clone()s instead of cloning the Regex itself. PregPattern::resolve() returns a ResolvedPattern enum (Arc or 'static reference) rather than an owned Regex, so neither path ever clones the Regex proper. Co-Authored-By: Claude Sonnet 5 --- crates/shirabe/src/package/loader/array_loader.rs | 8 ++++---- .../src/package/loader/root_package_loader.rs | 22 ++++++++++++-------- .../src/package/loader/validating_array_loader.rs | 24 ++++++++++++---------- 3 files changed, 31 insertions(+), 23 deletions(-) (limited to 'crates/shirabe/src/package/loader') diff --git a/crates/shirabe/src/package/loader/array_loader.rs b/crates/shirabe/src/package/loader/array_loader.rs index b85fbe61..07937c31 100644 --- a/crates/shirabe/src/package/loader/array_loader.rs +++ b/crates/shirabe/src/package/loader/array_loader.rs @@ -20,7 +20,7 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; use shirabe_php_shim::{ E_USER_DEPRECATED, PhpMixed, UnexpectedValueException, is_scalar, is_string, json_encode, - ltrim, stripos, strpos, strtolower, strval, substr, trigger_error, trim, + ltrim, php_regex, stripos, strpos, strtolower, strval, substr, trigger_error, trim, }; #[derive(Debug)] @@ -504,7 +504,7 @@ impl ArrayLoader { && !shirabe_php_shim::empty(time_value) { let time_str = time_value.as_string().unwrap_or(""); - let time = if Preg::is_match(r"/^\d++$/D", time_str) { + let time = if Preg::is_match(php_regex!(r"/^\d++$/D"), time_str) { format!("@{}", time_str) } else { time_str.to_string() @@ -674,7 +674,7 @@ impl ArrayLoader { if let Some(alias_normalized) = alias_normalized && !alias_normalized.is_empty() { - let pretty_alias = Preg::replace(r"{(\.9{7})+}", ".x", &alias_normalized); + let pretty_alias = Preg::replace(php_regex!(r"{(\.9{7})+}"), ".x", &alias_normalized); return Ok(match package { CompleteOrRootPackage::Root(root) => RootAliasPackageHandle::new( @@ -938,7 +938,7 @@ impl ArrayLoader { && default_branch_is_true && self .version_parser - .parse_numeric_alias_prefix(&Preg::replace(r"{^v}", "", &version_str)) + .parse_numeric_alias_prefix(&Preg::replace(php_regex!(r"{^v}"), "", &version_str)) .is_none() { return Ok(Some(VersionParser::DEFAULT_BRANCH_ALIAS.to_string())); diff --git a/crates/shirabe/src/package/loader/root_package_loader.rs b/crates/shirabe/src/package/loader/root_package_loader.rs index 83cda30b..b3ed8c41 100644 --- a/crates/shirabe/src/package/loader/root_package_loader.rs +++ b/crates/shirabe/src/package/loader/root_package_loader.rs @@ -16,7 +16,9 @@ use crate::util::Platform; use crate::util::ProcessExecutor; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; -use shirabe_php_shim::{PhpMixed, RuntimeException, UnexpectedValueException, strtolower}; +use shirabe_php_shim::{ + PhpMixed, RuntimeException, UnexpectedValueException, php_regex, strtolower, +}; #[derive(Debug)] pub struct RootPackageLoader { @@ -261,7 +263,7 @@ impl RootPackageLoader { for (req_name, req_version) in requires { let mut m: IndexMap = IndexMap::new(); if Preg::is_match3( - r"{(?:^|\| *|, *)([^,\s#|]+)(?:#[^ ]+)? +as +([^,\s|]+)(?:$| *\|| *,)}", + php_regex!(r"{(?:^|\| *|, *)([^,\s#|]+)(?:#[^ ]+)? +as +([^,\s|]+)(?:$| *\|| *,)}"), req_version, Some(&mut m), ) { @@ -315,7 +317,7 @@ impl RootPackageLoader { for (req_name, req_version) in requires { let mut constraints: Vec = vec![]; - let or_split = Preg::split(r"{\s*\|\|?\s*}", req_version.trim()); + let or_split = Preg::split(php_regex!(r"{\s*\|\|?\s*}"), req_version.trim()); for or_constraint in &or_split { let and_split = shirabe_semver::split_and_constraints(or_constraint); for and_constraint in and_split { @@ -348,8 +350,9 @@ impl RootPackageLoader { } for constraint in &constraints { - let req_version_stripped = Preg::replace(r"{^([^,\s@]+) as .+$}", "$1", constraint); - if Preg::is_match(r"{^[^,\s@]+$}", &req_version_stripped) { + let req_version_stripped = + Preg::replace(php_regex!(r"{^([^,\s@]+) as .+$}"), "$1", constraint); + if Preg::is_match(php_regex!(r"{^[^,\s@]+$}"), &req_version_stripped) { let stability_name = VersionParser::parse_stability(&req_version_stripped); if stability_name != "stable" { let name = strtolower(req_name); @@ -373,10 +376,13 @@ impl RootPackageLoader { mut references: IndexMap, ) -> IndexMap { for (req_name, req_version) in requires { - let req_version = Preg::replace(r"{^([^,\s@]+) as .+$}", "$1", req_version); + let req_version = Preg::replace(php_regex!(r"{^([^,\s@]+) as .+$}"), "$1", req_version); let mut m: IndexMap = IndexMap::new(); - if Preg::is_match3(r"{^[^,\s@]+?#([a-f0-9]+)$}", &req_version, Some(&mut m)) - && VersionParser::parse_stability(&req_version) == "dev" + if Preg::is_match3( + php_regex!(r"{^[^,\s@]+?#([a-f0-9]+)$}"), + &req_version, + Some(&mut m), + ) && VersionParser::parse_stability(&req_version) == "dev" { let name = strtolower(req_name); references.insert( diff --git a/crates/shirabe/src/package/loader/validating_array_loader.rs b/crates/shirabe/src/package/loader/validating_array_loader.rs index 4d7965f8..28ae7d9b 100644 --- a/crates/shirabe/src/package/loader/validating_array_loader.rs +++ b/crates/shirabe/src/package/loader/validating_array_loader.rs @@ -11,8 +11,8 @@ use shirabe_external_packages::composer::pcre::Preg; use shirabe_php_shim::{ E_USER_DEPRECATED, PHP_EOL, PhpMixed, array_intersect_key, array_values, filter_var_email, get_debug_type, is_array, is_bool, is_int, is_numeric, is_scalar, is_string, json_encode, - parse_url_all, php_to_string, str_replace, strcasecmp, strtolower, strtotime, substr, - trigger_error, trim, var_export, + parse_url_all, php_regex, php_to_string, str_replace, strcasecmp, strtolower, strtotime, + substr, trigger_error, trim, var_export, }; use shirabe_semver::Intervals; use shirabe_semver::constraint::AnyConstraint; @@ -910,7 +910,7 @@ impl LoaderInterface for ValidatingArrayLoader { self.warnings .borrow_mut() .push(format!("{}.{}", link_type, err)); - } else if !Preg::is_match("{^[A-Za-z0-9_./-]+$}", &package) { + } else if !Preg::is_match(php_regex!("{^[A-Za-z0-9_./-]+$}"), &package) { self.errors.borrow_mut().push(format!( "{}.{} : invalid key, package names must be strings containing only [A-Za-z0-9_./-]", link_type, package @@ -1173,7 +1173,7 @@ impl LoaderInterface for ValidatingArrayLoader { } if let Some(ref_val) = section.get("reference").filter(|_| isset("reference")) { let ref_str = php_to_string(ref_val); - if Preg::is_match("{^\\s*-}", &ref_str) { + if Preg::is_match(php_regex!("{^\\s*-}"), &ref_str) { self.errors.borrow_mut().push(format!( "{}.reference : must not start with a \"-\", \"{}\" given", src_type, ref_str @@ -1182,7 +1182,7 @@ impl LoaderInterface for ValidatingArrayLoader { } if let Some(url_val) = section.get("url").filter(|_| isset("url")) { let url_str = php_to_string(url_val); - if Preg::is_match("{^\\s*-}", &url_str) { + if Preg::is_match(php_regex!("{^\\s*-}"), &url_str) { self.errors.borrow_mut().push(format!( "{}.url : must not start with a \"-\", \"{}\" given", src_type, url_str @@ -1336,7 +1336,9 @@ impl ValidatingArrayLoader { } if !Preg::is_match( - "{^[a-z0-9](?:[_.-]?[a-z0-9]++)*+/[a-z0-9](?:(?:[_.]|-{1,2})?[a-z0-9]++)*+$}iD", + php_regex!( + "{^[a-z0-9](?:[_.-]?[a-z0-9]++)*+/[a-z0-9](?:(?:[_.]|-{1,2})?[a-z0-9]++)*+$}iD" + ), name, ) { return Some(format!( @@ -1359,14 +1361,14 @@ impl ValidatingArrayLoader { )); } - if Preg::is_match("{\\.json$}", name) { + if Preg::is_match(php_regex!("{\\.json$}"), name) { return Some(format!( "{} is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.", name )); } - if Preg::is_match("{[A-Z]}", name) { + if Preg::is_match(php_regex!("{[A-Z]}"), name) { if is_link { return Some(format!( "{} is invalid, it should not contain uppercase characters. Please use {} instead.", @@ -1376,7 +1378,7 @@ impl ValidatingArrayLoader { } let suggest_name = Preg::replace( - "{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}", + php_regex!("{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}"), "\\1\\3-\\2\\4", name, ); @@ -1400,7 +1402,7 @@ impl ValidatingArrayLoader { .as_string() .unwrap_or("") .to_string(); - if !Preg::is_match(&format!("{{^{}$}}u", regex), &value) { + if !Preg::is_match(format!("{{^{}$}}u", regex), &value) { let message = format!( "{} : invalid value ({}), must match {}", property, value, regex @@ -1513,7 +1515,7 @@ impl ValidatingArrayLoader { if let Some(regex_str) = regex { let value_str = php_to_string(&value); - if !Preg::is_match(&format!("{{^{}$}}u", regex_str), &value_str) { + if !Preg::is_match(format!("{{^{}$}}u", regex_str), &value_str) { self.warnings.borrow_mut().push(format!( "{}.{} : invalid value ({}), must match {}", property, key, value_str, regex_str -- cgit v1.3.1