Skip to content

Commit a812905

Browse files
Yhg1sblurb-it[bot]
authored andcommitted
gh-142183: Cache one datachunk per tstate to prevent alloc/dealloc thrashing (GH-145789)
Cache one datachunk per tstate to prevent alloc/dealloc thrashing when repeatedly hitting the same call depth at exactly the wrong boundary. --------- (cherry picked from commit 706fd4e) Co-authored-by: T. Wouters <thomas@python.org> Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
1 parent 717ebd7 commit a812905

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
@@ -180,6 +180,7 @@ struct _ts {
180180
_PyStackChunk *datastack_chunk;
181181
PyObject **datastack_top;
182182
PyObject **datastack_limit;
183+
_PyStackChunk *datastack_cached_chunk;
183184
/* XXX signal handlers should also be here */
184185

185186
/* 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
@@ -1575,6 +1575,7 @@ init_threadstate(_PyThreadStateImpl *_tstate,
15751575
tstate->datastack_chunk = NULL;
15761576
tstate->datastack_top = NULL;
15771577
tstate->datastack_limit = NULL;
1578+
tstate->datastack_cached_chunk = NULL;
15781579
tstate->what_event = -1;
15791580
tstate->current_executor = NULL;
15801581
tstate->dict_global_version = 0;
@@ -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
@@ -3029,9 +3035,20 @@ push_chunk(PyThreadState *tstate, int size)
30293035
while (allocate_size < (int)sizeof(PyObject*)*(size + MINIMUM_OVERHEAD)) {
30303036
allocate_size *= 2;
30313037
}
3032-
_PyStackChunk *new = allocate_chunk(allocate_size, tstate->datastack_chunk);
3033-
if (new == NULL) {
3034-
return NULL;
3038+
_PyStackChunk *new;
3039+
if (tstate->datastack_cached_chunk != NULL
3040+
&& (size_t)allocate_size <= tstate->datastack_cached_chunk->size)
3041+
{
3042+
new = tstate->datastack_cached_chunk;
3043+
tstate->datastack_cached_chunk = NULL;
3044+
new->previous = tstate->datastack_chunk;
3045+
new->top = 0;
3046+
}
3047+
else {
3048+
new = allocate_chunk(allocate_size, tstate->datastack_chunk);
3049+
if (new == NULL) {
3050+
return NULL;
3051+
}
30353052
}
30363053
if (tstate->datastack_chunk) {
30373054
tstate->datastack_chunk->top = tstate->datastack_top -
@@ -3067,12 +3084,17 @@ _PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFrame * frame)
30673084
if (base == &tstate->datastack_chunk->data[0]) {
30683085
_PyStackChunk *chunk = tstate->datastack_chunk;
30693086
_PyStackChunk *previous = chunk->previous;
3087+
_PyStackChunk *cached = tstate->datastack_cached_chunk;
30703088
// push_chunk ensures that the root chunk is never popped:
30713089
assert(previous);
30723090
tstate->datastack_top = &previous->data[previous->top];
30733091
tstate->datastack_chunk = previous;
3074-
_PyObject_VirtualFree(chunk, chunk->size);
30753092
tstate->datastack_limit = (PyObject **)(((char *)previous) + previous->size);
3093+
chunk->previous = NULL;
3094+
if (cached != NULL) {
3095+
_PyObject_VirtualFree(cached, cached->size);
3096+
}
3097+
tstate->datastack_cached_chunk = chunk;
30763098
}
30773099
else {
30783100
assert(tstate->datastack_top);

0 commit comments

Comments
 (0)