From 23860a736f539064a64284a673a1b0e69d24e980 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 5 Jul 2026 00:59:45 +0900 Subject: perf(pcre): cache compiled regex patterns across preg_* calls Composer's classmap generator re-issues the same PHP-derived pattern string for every scanned file (and every token within it), relying on PCRE's built-in compiled-pattern cache to make that free. shirabe had no equivalent, so every Preg::* call recompiled the pattern from scratch via regex::Regex::new(), making `composer create-project` autoload generation ~130x slower than Composer on a fresh laravel/laravel install (130s vs ~1s). Memoizing compiled patterns by their raw string in compile_php_pattern closes that gap. Co-Authored-By: Claude Sonnet 5 --- crates/shirabe-php-shim/src/preg.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'crates') diff --git a/crates/shirabe-php-shim/src/preg.rs b/crates/shirabe-php-shim/src/preg.rs index 4b43467..3da8ae7 100644 --- a/crates/shirabe-php-shim/src/preg.rs +++ b/crates/shirabe-php-shim/src/preg.rs @@ -1,3 +1,6 @@ +use indexmap::IndexMap; +use std::sync::{LazyLock, Mutex}; + pub const PREG_PATTERN_ORDER: i64 = 1; pub const PREG_SET_ORDER: i64 = 2; pub const PREG_OFFSET_CAPTURE: i64 = 256; @@ -439,7 +442,28 @@ fn translate_pcre_literals(inner: &str) -> String { out } +// PHP's PCRE engine keeps a per-process cache of compiled patterns (pcre.cache_size, default 4096), +// so repeated preg_* calls with the same pattern string are effectively free. The `regex` crate has +// no such cache, and callers like the classmap generator re-issue the same pattern string for every +// file (or even every scanned token), so compilation must be memoized here to match PHP's amortized +// cost. +static PATTERN_CACHE: LazyLock>> = + LazyLock::new(|| Mutex::new(IndexMap::new())); + fn compile_php_pattern(pattern: &str) -> anyhow::Result<(regex::Regex, bool)> { + if let Some(cached) = PATTERN_CACHE.lock().unwrap().get(pattern) { + return Ok(cached.clone()); + } + + let compiled = compile_php_pattern_uncached(pattern)?; + PATTERN_CACHE + .lock() + .unwrap() + .insert(pattern.to_string(), compiled.clone()); + Ok(compiled) +} + +fn compile_php_pattern_uncached(pattern: &str) -> anyhow::Result<(regex::Regex, bool)> { let delimiter = pattern .chars() .next() -- cgit v1.3.1