From 45de3ae190d091801bae9ea8b7c0a4e84f7c14b0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 6 Jul 2026 07:40:05 +0300 Subject: [PATCH] gh-153161: Cache the match object in the compiled re pattern A dead match object is kept in the pattern (it retains only its type reference) and resurrected for the next match instead of allocating a new one. Matches of one pattern all have the same size, and the next match is typically created just before the previous one dies. Co-Authored-By: Claude Opus 4.8 --- ...-07-06-12-00-00.gh-issue-153161.MtchC5.rst | 3 ++ Modules/_sre/sre.c | 47 ++++++++++++++++--- Modules/_sre/sre.h | 2 + 3 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-06-12-00-00.gh-issue-153161.MtchC5.rst diff --git a/Misc/NEWS.d/next/Library/2026-07-06-12-00-00.gh-issue-153161.MtchC5.rst b/Misc/NEWS.d/next/Library/2026-07-06-12-00-00.gh-issue-153161.MtchC5.rst new file mode 100644 index 00000000000000..05f368d4162808 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-06-12-00-00.gh-issue-153161.MtchC5.rst @@ -0,0 +1,3 @@ +Speed up successful matching in :mod:`re` by 15-18 ns: a dead match object +is kept in the compiled pattern and reused for the next match instead of +allocating a new one. diff --git a/Modules/_sre/sre.c b/Modules/_sre/sre.c index 7d03b909226f24..72d0243c958a0a 100644 --- a/Modules/_sre/sre.c +++ b/Modules/_sre/sre.c @@ -44,6 +44,7 @@ static const char copyright[] = #include "pycore_long.h" // _PyLong_GetZero() #include "pycore_list.h" // _PyList_AppendTakeRef() #include "pycore_moduleobject.h" // _PyModule_GetState() +#include "pycore_object.h" // _Py_NewReference() #include "pycore_tuple.h" // _PyTuple_FromPairSteal #include "pycore_unicodeobject.h" // _PyUnicode_Copy #include "pycore_unicodectype.h" // _PyUnicode_IsXidStart() @@ -875,6 +876,12 @@ static int pattern_clear(PyObject *op) { PatternObject *self = _PatternObject_CAST(op); + PyObject *cached = _Py_atomic_exchange_ptr(&self->cached_match, NULL); + if (cached != NULL) { + PyTypeObject *tp = Py_TYPE(cached); + PyObject_GC_Del(cached); + Py_DECREF(tp); + } Py_CLEAR(self->groupindex); Py_CLEAR(self->indexgroup); Py_CLEAR(self->pattern); @@ -1788,6 +1795,7 @@ _sre_compile_impl(PyObject *module, PyObject *pattern, int flags, if (!self) return NULL; self->weakreflist = NULL; + self->cached_match = NULL; self->pattern = NULL; self->groupindex = NULL; self->indexgroup = NULL; @@ -2505,7 +2513,24 @@ match_dealloc(PyObject *self) { PyTypeObject *tp = Py_TYPE(self); PyObject_GC_UnTrack(self); - (void)match_clear(self); + MatchObject *match = _MatchObject_CAST(self); + PatternObject *pattern = match->pattern; + Py_CLEAR(match->string); + Py_CLEAR(match->regs); + if (pattern != NULL) { + match->pattern = NULL; + /* Cache the dead object (it keeps only its type reference) in the + pattern for the next match. The pattern reference is given up + either way; if it was the last one, pattern_clear() frees the + object just cached. */ + PyObject *expected = NULL; + if (_Py_atomic_compare_exchange_ptr(&pattern->cached_match, + &expected, self)) { + Py_DECREF(pattern); + return; + } + Py_DECREF(pattern); + } tp->tp_free(self); Py_DECREF(tp); } @@ -2950,12 +2975,20 @@ pattern_new_match(_sremodulestate* module_state, if (status > 0) { /* create match object (with room for extra group marks) */ - /* coverity[ampersand_in_size] */ - match = PyObject_GC_NewVar(MatchObject, - module_state->Match_Type, - 2*(pattern->groups+1)); - if (!match) - return NULL; + match = (MatchObject *)_Py_atomic_exchange_ptr(&pattern->cached_match, + NULL); + if (match != NULL) { + assert(Py_SIZE(match) == 2*(pattern->groups+1)); + _Py_NewReference((PyObject *)match); + } + else { + /* coverity[ampersand_in_size] */ + match = PyObject_GC_NewVar(MatchObject, + module_state->Match_Type, + 2*(pattern->groups+1)); + if (!match) + return NULL; + } Py_INCREF(pattern); match->pattern = pattern; diff --git a/Modules/_sre/sre.h b/Modules/_sre/sre.h index 42681c2addf3c2..da3315cbc5442f 100644 --- a/Modules/_sre/sre.h +++ b/Modules/_sre/sre.h @@ -33,6 +33,8 @@ typedef struct { PyObject* pattern; /* pattern source (or None) */ int flags; /* flags used when compiling pattern source */ PyObject *weakreflist; /* List of weak references */ + PyObject *cached_match; /* dead match object kept for reuse; owns only + a reference to its type */ int isbytes; /* pattern type (1 - bytes, 0 - string, -1 - None) */ #ifdef Py_DEBUG /* for simulation of user interruption */