[3.13] gh-142183: Cache one datachunk per tstate to prevent alloc/dealloc thrashing (GH-145789) (#146123)

* gh-142183: Cache one datachunk per tstate to prevent alloc/dealloc thrashing (GH-145789) (#145828)

Cache one datachunk per tstate to prevent alloc/dealloc thrashing when repeatedly hitting the same call depth at exactly the wrong boundary.

Move new _ts member to the end to not mess up remote debuggers' ideas of the
struct's layout. (The struct is only created by the runtime, and the new
field only used by the runtime, so it should be safe.)

(cherry picked from commit 706fd4ec08)
(cherry picked from commit 19cbcc0f85)
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Co-authored-by: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
This commit is contained in:
T. Wouters 2026-03-24 02:27:57 +01:00 committed by GitHub
parent 0d82ce82f1
commit 684e7af8cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 663 additions and 635 deletions

View file

@ -1521,6 +1521,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->previous_executor = NULL;
tstate->dict_global_version = 0;
@ -1655,6 +1656,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
@ -2934,9 +2940,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 -
@ -2972,12 +2989,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);