]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-142183: Cache one datachunk per tstate to prevent alloc/dealloc thrashing (#145789)
authorT. Wouters <thomas@python.org>
Wed, 11 Mar 2026 14:46:16 +0000 (15:46 +0100)
committerGitHub <noreply@github.com>
Wed, 11 Mar 2026 14:46:16 +0000 (15:46 +0100)
Cache one datachunk per tstate to prevent alloc/dealloc thrashing when repeatedly hitting the same call depth at exactly the wrong boundary.

---------

Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Include/cpython/pystate.h
Misc/NEWS.d/next/Core_and_Builtins/2026-03-11-00-13-59.gh-issue-142183.2iVhJH.rst [new file with mode: 0644]
Python/pystate.c

index 22df26bd37a5c567927b27a716a8836bc94e4a4d..1c56ad5af8072f39a236b22f9f217b0d60a2503b 100644 (file)
@@ -198,6 +198,7 @@ struct _ts {
     _PyStackChunk *datastack_chunk;
     PyObject **datastack_top;
     PyObject **datastack_limit;
+    _PyStackChunk *datastack_cached_chunk;
     /* XXX signal handlers should also be here */
 
     /* The following fields are here to avoid allocation during init.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-11-00-13-59.gh-issue-142183.2iVhJH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-11-00-13-59.gh-issue-142183.2iVhJH.rst
new file mode 100644 (file)
index 0000000..827224d
--- /dev/null
@@ -0,0 +1 @@
+Avoid a pathological case where repeated calls at a specific stack depth could be significantly slower.
index a8f37bedc812472687a9a6a237db9fd6a902206a..17b8430b19c188ba10c1502aa0dad6ac28a84dc4 100644 (file)
@@ -1564,6 +1564,7 @@ init_threadstate(_PyThreadStateImpl *_tstate,
     tstate->datastack_chunk = NULL;
     tstate->datastack_top = NULL;
     tstate->datastack_limit = NULL;
+    tstate->datastack_cached_chunk = NULL;
     tstate->what_event = -1;
     tstate->current_executor = NULL;
     tstate->jit_exit = NULL;
@@ -1714,6 +1715,11 @@ clear_datastack(PyThreadState *tstate)
         _PyObject_VirtualFree(chunk, chunk->size);
         chunk = prev;
     }
+    if (tstate->datastack_cached_chunk != NULL) {
+        _PyObject_VirtualFree(tstate->datastack_cached_chunk,
+                              tstate->datastack_cached_chunk->size);
+        tstate->datastack_cached_chunk = NULL;
+    }
 }
 
 void
@@ -3045,9 +3051,20 @@ push_chunk(PyThreadState *tstate, int size)
     while (allocate_size < (int)sizeof(PyObject*)*(size + MINIMUM_OVERHEAD)) {
         allocate_size *= 2;
     }
-    _PyStackChunk *new = allocate_chunk(allocate_size, tstate->datastack_chunk);
-    if (new == NULL) {
-        return NULL;
+    _PyStackChunk *new;
+    if (tstate->datastack_cached_chunk != NULL
+        && (size_t)allocate_size <= tstate->datastack_cached_chunk->size)
+    {
+        new = tstate->datastack_cached_chunk;
+        tstate->datastack_cached_chunk = NULL;
+        new->previous = tstate->datastack_chunk;
+        new->top = 0;
+    }
+    else {
+        new = allocate_chunk(allocate_size, tstate->datastack_chunk);
+        if (new == NULL) {
+            return NULL;
+        }
     }
     if (tstate->datastack_chunk) {
         tstate->datastack_chunk->top = tstate->datastack_top -
@@ -3083,12 +3100,17 @@ _PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFrame * frame)
     if (base == &tstate->datastack_chunk->data[0]) {
         _PyStackChunk *chunk = tstate->datastack_chunk;
         _PyStackChunk *previous = chunk->previous;
+        _PyStackChunk *cached = tstate->datastack_cached_chunk;
         // push_chunk ensures that the root chunk is never popped:
         assert(previous);
         tstate->datastack_top = &previous->data[previous->top];
         tstate->datastack_chunk = previous;
-        _PyObject_VirtualFree(chunk, chunk->size);
         tstate->datastack_limit = (PyObject **)(((char *)previous) + previous->size);
+        chunk->previous = NULL;
+        if (cached != NULL) {
+            _PyObject_VirtualFree(cached, cached->size);
+        }
+        tstate->datastack_cached_chunk = chunk;
     }
     else {
         assert(tstate->datastack_top);