Skip to content

Commit 706fd4e

Browse files
Yhg1sblurb-it[bot]
andauthored
gh-142183: Cache one datachunk per tstate to prevent alloc/dealloc thrashing (#145789)
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>
1 parent 4722202 commit 706fd4e

File tree

3 files changed

+28
-4
lines changed

3 files changed

+28
-4
lines changed

Include/cpython/pystate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ struct _ts {
198198
_PyStackChunk *datastack_chunk;
199199
PyObject **datastack_top;
200200
PyObject **datastack_limit;
201+
_PyStackChunk *datastack_cached_chunk;
201202
/* XXX signal handlers should also be here */
202203

203204
/* The following fields are here to avoid allocation during init.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Avoid a pathological case where repeated calls at a specific stack depth could be significantly slower.

Python/pystate.c

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,6 +1564,7 @@ init_threadstate(_PyThreadStateImpl *_tstate,
15641564
tstate->datastack_chunk = NULL;
15651565
tstate->datastack_top = NULL;
15661566
tstate->datastack_limit = NULL;
1567+
tstate->datastack_cached_chunk = NULL;
15671568
tstate->what_event = -1;
15681569
tstate->current_executor = NULL;
15691570
tstate->jit_exit = NULL;
@@ -1714,6 +1715,11 @@ clear_datastack(PyThreadState *tstate)
17141715
_PyObject_VirtualFree(chunk, chunk->size);
17151716
chunk = prev;
17161717
}
1718+
if (tstate->datastack_cached_chunk != NULL) {
1719+
_PyObject_VirtualFree(tstate->datastack_cached_chunk,
1720+
tstate->datastack_cached_chunk->size);
1721+
tstate->datastack_cached_chunk = NULL;
1722+
}
17171723
}
17181724

17191725
void
@@ -3045,9 +3051,20 @@ push_chunk(PyThreadState *tstate, int size)
30453051
while (allocate_size < (int)sizeof(PyObject*)*(size + MINIMUM_OVERHEAD)) {
30463052
allocate_size *= 2;
30473053
}
3048-
_PyStackChunk *new = allocate_chunk(allocate_size, tstate->datastack_chunk);
3049-
if (new == NULL) {
3050-
return NULL;
3054+
_PyStackChunk *new;
3055+
if (tstate->datastack_cached_chunk != NULL
3056+
&& (size_t)allocate_size <= tstate->datastack_cached_chunk->size)
3057+
{
3058+
new = tstate->datastack_cached_chunk;
3059+
tstate->datastack_cached_chunk = NULL;
3060+
new->previous = tstate->datastack_chunk;
3061+
new->top = 0;
3062+
}
3063+
else {
3064+
new = allocate_chunk(allocate_size, tstate->datastack_chunk);
3065+
if (new == NULL) {
3066+
return NULL;
3067+
}
30513068
}
30523069
if (tstate->datastack_chunk) {
30533070
tstate->datastack_chunk->top = tstate->datastack_top -
@@ -3083,12 +3100,17 @@ _PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFrame * frame)
30833100
if (base == &tstate->datastack_chunk->data[0]) {
30843101
_PyStackChunk *chunk = tstate->datastack_chunk;
30853102
_PyStackChunk *previous = chunk->previous;
3103+
_PyStackChunk *cached = tstate->datastack_cached_chunk;
30863104
// push_chunk ensures that the root chunk is never popped:
30873105
assert(previous);
30883106
tstate->datastack_top = &previous->data[previous->top];
30893107
tstate->datastack_chunk = previous;
3090-
_PyObject_VirtualFree(chunk, chunk->size);
30913108
tstate->datastack_limit = (PyObject **)(((char *)previous) + previous->size);
3109+
chunk->previous = NULL;
3110+
if (cached != NULL) {
3111+
_PyObject_VirtualFree(cached, cached->size);
3112+
}
3113+
tstate->datastack_cached_chunk = chunk;
30923114
}
30933115
else {
30943116
assert(tstate->datastack_top);

0 commit comments

Comments
 (0)