[3.14] 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)
Co-authored-by: T. Wouters <thomas@python.org>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
This commit is contained in:
Miss Islington (bot) 2026-03-18 16:47:36 +01:00 committed by GitHub
parent 6980b94c3a
commit 19cbcc0f85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 4347 additions and 4310 deletions

View file

@ -1575,6 +1575,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->dict_global_version = 0;
@ -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
@ -3029,9 +3035,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 -
@ -3067,12 +3084,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);