mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
mruby-regexp: cache Pike VM state in pattern to avoid per-exec malloc
Pre-allocate visited array and thread lists at compile time and reuse them across re_exec calls. A cache_in_use flag detects re-entrancy and falls back to malloc when needed. Eliminates 3 malloc + 3 free per NFA execution for the common non-re-entrant case. Combined with the literal fast path, brings literal match? from 4.7x to 2.5x vs CRuby. Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -75,6 +75,11 @@ typedef struct mrb_regexp_pattern {
|
||||
uint8_t first_bytes[16]; /* bitmap of possible first bytes (128-bit, ASCII) */
|
||||
mrb_bool has_first_bytes; /* true if first_bytes is usable for skipping */
|
||||
mrb_bool is_literal; /* true if pattern is pure literal (no metacharacters) */
|
||||
/* Cached VM state for pike_vm (avoids malloc per re_exec call) */
|
||||
uint32_t *cached_visited; /* generation-based visited array */
|
||||
void *cached_threads[2]; /* curr/next thread lists */
|
||||
int cached_list_capa; /* capacity of cached thread lists */
|
||||
mrb_bool cache_in_use; /* re-entrancy guard */
|
||||
} mrb_regexp_pattern;
|
||||
|
||||
/* Regexp flags */
|
||||
@@ -94,6 +99,12 @@ typedef struct mrb_regexp_pattern {
|
||||
/* Maximum captures */
|
||||
#define RE_MAX_CAPTURES 32
|
||||
|
||||
/* Thread struct for Pike VM (also used for cache sizing) */
|
||||
typedef struct {
|
||||
uint32_t pc;
|
||||
int cap_slot;
|
||||
} re_thread_cache;
|
||||
|
||||
/* Compile a pattern string into bytecode */
|
||||
mrb_regexp_pattern* re_compile(mrb_state *mrb, const char *pattern, mrb_int len, uint32_t flags);
|
||||
|
||||
|
||||
@@ -902,6 +902,16 @@ re_compile(mrb_state *mrb, const char *pattern, mrb_int len, uint32_t flags)
|
||||
}
|
||||
}
|
||||
|
||||
/* Pre-allocate VM state cache for pike_vm */
|
||||
{
|
||||
int list_capa = (int)pat->code_len * 2 + 16;
|
||||
pat->cached_visited = (uint32_t*)mrb_calloc(mrb, pat->code_len + 1, sizeof(uint32_t));
|
||||
pat->cached_threads[0] = mrb_malloc(mrb, sizeof(re_thread_cache) * list_capa);
|
||||
pat->cached_threads[1] = mrb_malloc(mrb, sizeof(re_thread_cache) * list_capa);
|
||||
pat->cached_list_capa = list_capa;
|
||||
pat->cache_in_use = FALSE;
|
||||
}
|
||||
|
||||
if (c.stripped) mrb_free(mrb, c.stripped);
|
||||
return pat;
|
||||
}
|
||||
@@ -914,6 +924,9 @@ re_free(mrb_state *mrb, mrb_regexp_pattern *pat)
|
||||
mrb_free(mrb, pat->classes);
|
||||
mrb_free(mrb, pat->named_captures);
|
||||
mrb_free(mrb, pat->prefix);
|
||||
mrb_free(mrb, pat->cached_visited);
|
||||
mrb_free(mrb, pat->cached_threads[0]);
|
||||
mrb_free(mrb, pat->cached_threads[1]);
|
||||
mrb_free(mrb, pat);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,10 +55,7 @@ class_match(const re_charclass *cc, uint8_t ch)
|
||||
* - Threads reference captures by pool index, avoiding 260-byte struct copies
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
uint32_t pc;
|
||||
int cap_slot; /* slot index into capture pool */
|
||||
} re_thread;
|
||||
typedef re_thread_cache re_thread;
|
||||
|
||||
typedef struct {
|
||||
re_thread *threads;
|
||||
@@ -221,6 +218,11 @@ pike_vm(mrb_state *mrb, const mrb_regexp_pattern *pat,
|
||||
|
||||
mrb_bool match_only = (captures == NULL || captures_size == 0);
|
||||
|
||||
/* Use cached VM state if available (avoids malloc per call) */
|
||||
mrb_regexp_pattern *mpat = (mrb_regexp_pattern*)pat; /* for cache_in_use flag */
|
||||
mrb_bool use_cache = !mpat->cache_in_use && mpat->cached_visited != NULL;
|
||||
if (use_cache) mpat->cache_in_use = TRUE;
|
||||
|
||||
pike_state s;
|
||||
s.mrb = mrb;
|
||||
s.pat = pat;
|
||||
@@ -231,7 +233,6 @@ pike_vm(mrb_state *mrb, const mrb_regexp_pattern *pat,
|
||||
s.match_only = match_only;
|
||||
s.gen = 1;
|
||||
if (match_only) {
|
||||
/* no capture tracking needed; allocate minimal pool (1 dummy slot) */
|
||||
s.pool_capa = 1;
|
||||
s.pool_next = 0;
|
||||
s.cap_pool = (int*)mrb_malloc(mrb, sizeof(int) * ncap);
|
||||
@@ -244,13 +245,22 @@ pike_vm(mrb_state *mrb, const mrb_regexp_pattern *pat,
|
||||
s.result_caps = (int*)mrb_malloc(mrb, sizeof(int) * ncap);
|
||||
memset(s.result_caps, -1, sizeof(int) * ncap);
|
||||
}
|
||||
s.visited = (uint32_t*)mrb_calloc(mrb, pat->code_len + 1, sizeof(uint32_t));
|
||||
|
||||
re_threadlist curr, next;
|
||||
curr.threads = (re_thread*)mrb_malloc(mrb, sizeof(re_thread) * list_capa);
|
||||
curr.count = 0; curr.capa = list_capa;
|
||||
next.threads = (re_thread*)mrb_malloc(mrb, sizeof(re_thread) * list_capa);
|
||||
next.count = 0; next.capa = list_capa;
|
||||
if (use_cache) {
|
||||
s.visited = mpat->cached_visited;
|
||||
memset(s.visited, 0, sizeof(uint32_t) * (pat->code_len + 1));
|
||||
curr.threads = (re_thread*)mpat->cached_threads[0];
|
||||
next.threads = (re_thread*)mpat->cached_threads[1];
|
||||
curr.capa = next.capa = mpat->cached_list_capa;
|
||||
}
|
||||
else {
|
||||
s.visited = (uint32_t*)mrb_calloc(mrb, pat->code_len + 1, sizeof(uint32_t));
|
||||
curr.threads = (re_thread*)mrb_malloc(mrb, sizeof(re_thread) * list_capa);
|
||||
next.threads = (re_thread*)mrb_malloc(mrb, sizeof(re_thread) * list_capa);
|
||||
curr.capa = next.capa = list_capa;
|
||||
}
|
||||
curr.count = next.count = 0;
|
||||
|
||||
for (; sp <= str_end; sp++) {
|
||||
if (!s.matched) {
|
||||
@@ -358,11 +368,16 @@ pike_vm(mrb_state *mrb, const mrb_regexp_pattern *pat,
|
||||
ret = ncap > 0 ? ncap : 1;
|
||||
}
|
||||
|
||||
mrb_free(mrb, curr.threads);
|
||||
mrb_free(mrb, next.threads);
|
||||
if (use_cache) {
|
||||
mpat->cache_in_use = FALSE;
|
||||
}
|
||||
else {
|
||||
mrb_free(mrb, curr.threads);
|
||||
mrb_free(mrb, next.threads);
|
||||
mrb_free(mrb, s.visited);
|
||||
}
|
||||
mrb_free(mrb, s.cap_pool);
|
||||
if (s.result_caps) mrb_free(mrb, s.result_caps);
|
||||
mrb_free(mrb, s.visited);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user