Skip to content

Commit 73e225b

Browse files
miss-islingtonblurb-it[bot]
authored andcommitted
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 706fd4e) (cherry picked from commit 19cbcc0) Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
1 parent 450e9ea commit 73e225b

3 files changed

Lines changed: 29 additions & 4 deletions

File tree

Include/cpython/pystate.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ struct _ts {
200200
The PyThreadObject must hold the only reference to this value.
201201
*/
202202
PyObject *threading_local_sentinel;
203+
204+
_PyStackChunk *datastack_cached_chunk;
203205
};
204206

205207
#ifdef Py_DEBUG
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
@@ -1521,6 +1521,7 @@ init_threadstate(_PyThreadStateImpl *_tstate,
15211521
tstate->datastack_chunk = NULL;
15221522
tstate->datastack_top = NULL;
15231523
tstate->datastack_limit = NULL;
1524+
tstate->datastack_cached_chunk = NULL;
15241525
tstate->what_event = -1;
15251526
tstate->previous_executor = NULL;
15261527
tstate->dict_global_version = 0;
@@ -1655,6 +1656,11 @@ clear_datastack(PyThreadState *tstate)
16551656
_PyObject_VirtualFree(chunk, chunk->size);
16561657
chunk = prev;
16571658
}
1659+
if (tstate->datastack_cached_chunk != NULL) {
1660+
_PyObject_VirtualFree(tstate->datastack_cached_chunk,
1661+
tstate->datastack_cached_chunk->size);
1662+
tstate->datastack_cached_chunk = NULL;
1663+
}
16581664
}
16591665

16601666
void
@@ -2934,9 +2940,20 @@ push_chunk(PyThreadState *tstate, int size)
29342940
while (allocate_size < (int)sizeof(PyObject*)*(size + MINIMUM_OVERHEAD)) {
29352941
allocate_size *= 2;
29362942
}
2937-
_PyStackChunk *new = allocate_chunk(allocate_size, tstate->datastack_chunk);
2938-
if (new == NULL) {
2939-
return NULL;
2943+
_PyStackChunk *new;
2944+
if (tstate->datastack_cached_chunk != NULL
2945+
&& (size_t)allocate_size <= tstate->datastack_cached_chunk->size)
2946+
{
2947+
new = tstate->datastack_cached_chunk;
2948+
tstate->datastack_cached_chunk = NULL;
2949+
new->previous = tstate->datastack_chunk;
2950+
new->top = 0;
2951+
}
2952+
else {
2953+
new = allocate_chunk(allocate_size, tstate->datastack_chunk);
2954+
if (new == NULL) {
2955+
return NULL;
2956+
}
29402957
}
29412958
if (tstate->datastack_chunk) {
29422959
tstate->datastack_chunk->top = tstate->datastack_top -
@@ -2972,12 +2989,17 @@ _PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFrame * frame)
29722989
if (base == &tstate->datastack_chunk->data[0]) {
29732990
_PyStackChunk *chunk = tstate->datastack_chunk;
29742991
_PyStackChunk *previous = chunk->previous;
2992+
_PyStackChunk *cached = tstate->datastack_cached_chunk;
29752993
// push_chunk ensures that the root chunk is never popped:
29762994
assert(previous);
29772995
tstate->datastack_top = &previous->data[previous->top];
29782996
tstate->datastack_chunk = previous;
2979-
_PyObject_VirtualFree(chunk, chunk->size);
29802997
tstate->datastack_limit = (PyObject **)(((char *)previous) + previous->size);
2998+
chunk->previous = NULL;
2999+
if (cached != NULL) {
3000+
_PyObject_VirtualFree(cached, cached->size);
3001+
}
3002+
tstate->datastack_cached_chunk = chunk;
29813003
}
29823004
else {
29833005
assert(tstate->datastack_top);

0 commit comments

Comments
 (0)