Skip to content

Commit 69ac6e5

Browse files
authored
bpo-40521: Make tuple free list per-interpreter (GH-20247)
Each interpreter now has its own tuple free lists: * Move tuple numfree and free_list arrays into PyInterpreterState. * Define PyTuple_MAXSAVESIZE and PyTuple_MAXFREELIST macros in pycore_interp.h. * Add _Py_tuple_state structure. Pass it explicitly to tuple_alloc(). * Add tstate parameter to _PyTuple_ClearFreeList() * Each interpreter now has its own empty tuple singleton.
1 parent dc24b8a commit 69ac6e5

File tree

7 files changed

+82
-62
lines changed

7 files changed

+82
-62
lines changed

Include/internal/pycore_gc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ PyAPI_FUNC(void) _PyGC_InitState(struct _gc_runtime_state *);
166166

167167
// Functions to clear types free lists
168168
extern void _PyFrame_ClearFreeList(void);
169-
extern void _PyTuple_ClearFreeList(void);
169+
extern void _PyTuple_ClearFreeList(PyThreadState *tstate);
170170
extern void _PyFloat_ClearFreeList(void);
171171
extern void _PyList_ClearFreeList(void);
172172
extern void _PyDict_ClearFreeList(void);

Include/internal/pycore_interp.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,26 @@ struct _Py_unicode_state {
6464
struct _Py_unicode_fs_codec fs_codec;
6565
};
6666

67+
/* Speed optimization to avoid frequent malloc/free of small tuples */
68+
#ifndef PyTuple_MAXSAVESIZE
69+
// Largest tuple to save on free list
70+
# define PyTuple_MAXSAVESIZE 20
71+
#endif
72+
#ifndef PyTuple_MAXFREELIST
73+
// Maximum number of tuples of each size to save
74+
# define PyTuple_MAXFREELIST 2000
75+
#endif
76+
77+
struct _Py_tuple_state {
78+
#if PyTuple_MAXSAVESIZE > 0
79+
/* Entries 1 up to PyTuple_MAXSAVESIZE are free lists,
80+
entry 0 is the empty tuple () of which at most one instance
81+
will be allocated. */
82+
PyTupleObject *free_list[PyTuple_MAXSAVESIZE];
83+
int numfree[PyTuple_MAXSAVESIZE];
84+
#endif
85+
};
86+
6787

6888
/* interpreter state */
6989

@@ -157,6 +177,7 @@ struct _is {
157177
*/
158178
PyLongObject* small_ints[_PY_NSMALLNEGINTS + _PY_NSMALLPOSINTS];
159179
#endif
180+
struct _Py_tuple_state tuple;
160181
};
161182

162183
/* Used by _PyImport_Cleanup() */

Include/internal/pycore_pylifecycle.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ extern PyStatus _PyGC_Init(PyThreadState *tstate);
6060

6161
extern void _PyFrame_Fini(void);
6262
extern void _PyDict_Fini(void);
63-
extern void _PyTuple_Fini(void);
63+
extern void _PyTuple_Fini(PyThreadState *tstate);
6464
extern void _PyList_Fini(void);
6565
extern void _PySet_Fini(void);
6666
extern void _PyBytes_Fini(void);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Each interpreter now has its own tuple free lists and empty tuple singleton.

Modules/gcmodule.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1025,8 +1025,9 @@ delete_garbage(PyThreadState *tstate, GCState *gcstate,
10251025
static void
10261026
clear_freelists(void)
10271027
{
1028+
PyThreadState *tstate = _PyThreadState_GET();
10281029
_PyFrame_ClearFreeList();
1029-
_PyTuple_ClearFreeList();
1030+
_PyTuple_ClearFreeList(tstate);
10301031
_PyFloat_ClearFreeList();
10311032
_PyList_ClearFreeList();
10321033
_PyDict_ClearFreeList();

Objects/tupleobject.c

Lines changed: 53 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,6 @@ class tuple "PyTupleObject *" "&PyTuple_Type"
1414

1515
#include "clinic/tupleobject.c.h"
1616

17-
/* Speed optimization to avoid frequent malloc/free of small tuples */
18-
#ifndef PyTuple_MAXSAVESIZE
19-
#define PyTuple_MAXSAVESIZE 20 /* Largest tuple to save on free list */
20-
#endif
21-
#ifndef PyTuple_MAXFREELIST
22-
#define PyTuple_MAXFREELIST 2000 /* Maximum number of tuples of each size to save */
23-
#endif
24-
25-
/* bpo-40521: tuple free lists are shared by all interpreters. */
26-
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
27-
# undef PyTuple_MAXSAVESIZE
28-
# define PyTuple_MAXSAVESIZE 0
29-
#endif
30-
31-
#if PyTuple_MAXSAVESIZE > 0
32-
/* Entries 1 up to PyTuple_MAXSAVESIZE are free lists, entry 0 is the empty
33-
tuple () of which at most one instance will be allocated.
34-
*/
35-
static PyTupleObject *free_list[PyTuple_MAXSAVESIZE];
36-
static int numfree[PyTuple_MAXSAVESIZE];
37-
#endif
38-
3917
static inline void
4018
tuple_gc_track(PyTupleObject *op)
4119
{
@@ -47,14 +25,14 @@ void
4725
_PyTuple_DebugMallocStats(FILE *out)
4826
{
4927
#if PyTuple_MAXSAVESIZE > 0
50-
int i;
51-
char buf[128];
52-
for (i = 1; i < PyTuple_MAXSAVESIZE; i++) {
28+
PyInterpreterState *interp = _PyInterpreterState_GET();
29+
struct _Py_tuple_state *state = &interp->tuple;
30+
for (int i = 1; i < PyTuple_MAXSAVESIZE; i++) {
31+
char buf[128];
5332
PyOS_snprintf(buf, sizeof(buf),
5433
"free %d-sized PyTupleObject", i);
55-
_PyDebugAllocatorStats(out,
56-
buf,
57-
numfree[i], _PyObject_VAR_SIZE(&PyTuple_Type, i));
34+
_PyDebugAllocatorStats(out, buf, state->numfree[i],
35+
_PyObject_VAR_SIZE(&PyTuple_Type, i));
5836
}
5937
#endif
6038
}
@@ -68,18 +46,18 @@ _PyTuple_DebugMallocStats(FILE *out)
6846
which wraps this function).
6947
*/
7048
static PyTupleObject *
71-
tuple_alloc(Py_ssize_t size)
49+
tuple_alloc(struct _Py_tuple_state *state, Py_ssize_t size)
7250
{
7351
PyTupleObject *op;
7452
if (size < 0) {
7553
PyErr_BadInternalCall();
7654
return NULL;
7755
}
7856
#if PyTuple_MAXSAVESIZE > 0
79-
if (size < PyTuple_MAXSAVESIZE && (op = free_list[size]) != NULL) {
57+
if (size < PyTuple_MAXSAVESIZE && (op = state->free_list[size]) != NULL) {
8058
assert(size != 0);
81-
free_list[size] = (PyTupleObject *) op->ob_item[0];
82-
numfree[size]--;
59+
state->free_list[size] = (PyTupleObject *) op->ob_item[0];
60+
state->numfree[size]--;
8361
/* Inline PyObject_InitVar */
8462
#ifdef Py_TRACE_REFS
8563
Py_SET_SIZE(op, size);
@@ -107,13 +85,15 @@ PyTuple_New(Py_ssize_t size)
10785
{
10886
PyTupleObject *op;
10987
#if PyTuple_MAXSAVESIZE > 0
110-
if (size == 0 && free_list[0]) {
111-
op = free_list[0];
88+
PyInterpreterState *interp = _PyInterpreterState_GET();
89+
struct _Py_tuple_state *state = &interp->tuple;
90+
if (size == 0 && state->free_list[0]) {
91+
op = state->free_list[0];
11292
Py_INCREF(op);
11393
return (PyObject *) op;
11494
}
11595
#endif
116-
op = tuple_alloc(size);
96+
op = tuple_alloc(state, size);
11797
if (op == NULL) {
11898
return NULL;
11999
}
@@ -122,8 +102,8 @@ PyTuple_New(Py_ssize_t size)
122102
}
123103
#if PyTuple_MAXSAVESIZE > 0
124104
if (size == 0) {
125-
free_list[0] = op;
126-
++numfree[0];
105+
state->free_list[0] = op;
106+
++state->numfree[0];
127107
Py_INCREF(op); /* extra INCREF so that this is never freed */
128108
}
129109
#endif
@@ -210,8 +190,11 @@ PyTuple_Pack(Py_ssize_t n, ...)
210190
return PyTuple_New(0);
211191
}
212192

193+
PyInterpreterState *interp = _PyInterpreterState_GET();
194+
struct _Py_tuple_state *state = &interp->tuple;
195+
213196
va_start(vargs, n);
214-
PyTupleObject *result = tuple_alloc(n);
197+
PyTupleObject *result = tuple_alloc(state, n);
215198
if (result == NULL) {
216199
va_end(vargs);
217200
return NULL;
@@ -233,22 +216,24 @@ PyTuple_Pack(Py_ssize_t n, ...)
233216
static void
234217
tupledealloc(PyTupleObject *op)
235218
{
236-
Py_ssize_t i;
237219
Py_ssize_t len = Py_SIZE(op);
238220
PyObject_GC_UnTrack(op);
239221
Py_TRASHCAN_BEGIN(op, tupledealloc)
240222
if (len > 0) {
241-
i = len;
242-
while (--i >= 0)
223+
Py_ssize_t i = len;
224+
while (--i >= 0) {
243225
Py_XDECREF(op->ob_item[i]);
226+
}
244227
#if PyTuple_MAXSAVESIZE > 0
228+
PyInterpreterState *interp = _PyInterpreterState_GET();
229+
struct _Py_tuple_state *state = &interp->tuple;
245230
if (len < PyTuple_MAXSAVESIZE &&
246-
numfree[len] < PyTuple_MAXFREELIST &&
231+
state->numfree[len] < PyTuple_MAXFREELIST &&
247232
Py_IS_TYPE(op, &PyTuple_Type))
248233
{
249-
op->ob_item[0] = (PyObject *) free_list[len];
250-
numfree[len]++;
251-
free_list[len] = op;
234+
op->ob_item[0] = (PyObject *) state->free_list[len];
235+
state->numfree[len]++;
236+
state->free_list[len] = op;
252237
goto done; /* return */
253238
}
254239
#endif
@@ -423,7 +408,9 @@ _PyTuple_FromArray(PyObject *const *src, Py_ssize_t n)
423408
return PyTuple_New(0);
424409
}
425410

426-
PyTupleObject *tuple = tuple_alloc(n);
411+
PyInterpreterState *interp = _PyInterpreterState_GET();
412+
struct _Py_tuple_state *state = &interp->tuple;
413+
PyTupleObject *tuple = tuple_alloc(state, n);
427414
if (tuple == NULL) {
428415
return NULL;
429416
}
@@ -481,7 +468,8 @@ tupleconcat(PyTupleObject *a, PyObject *bb)
481468
Py_TYPE(bb)->tp_name);
482469
return NULL;
483470
}
484-
#define b ((PyTupleObject *)bb)
471+
PyTupleObject *b = (PyTupleObject *)bb;
472+
485473
if (Py_SIZE(b) == 0 && PyTuple_CheckExact(a)) {
486474
Py_INCREF(a);
487475
return (PyObject *)a;
@@ -492,7 +480,9 @@ tupleconcat(PyTupleObject *a, PyObject *bb)
492480
return PyTuple_New(0);
493481
}
494482

495-
np = tuple_alloc(size);
483+
PyInterpreterState *interp = _PyInterpreterState_GET();
484+
struct _Py_tuple_state *state = &interp->tuple;
485+
np = tuple_alloc(state, size);
496486
if (np == NULL) {
497487
return NULL;
498488
}
@@ -512,7 +502,6 @@ tupleconcat(PyTupleObject *a, PyObject *bb)
512502
}
513503
tuple_gc_track(np);
514504
return (PyObject *)np;
515-
#undef b
516505
}
517506

518507
static PyObject *
@@ -536,7 +525,9 @@ tuplerepeat(PyTupleObject *a, Py_ssize_t n)
536525
if (n > PY_SSIZE_T_MAX / Py_SIZE(a))
537526
return PyErr_NoMemory();
538527
size = Py_SIZE(a) * n;
539-
np = tuple_alloc(size);
528+
PyInterpreterState *interp = _PyInterpreterState_GET();
529+
struct _Py_tuple_state *state = &interp->tuple;
530+
np = tuple_alloc(state, size);
540531
if (np == NULL)
541532
return NULL;
542533
p = np->ob_item;
@@ -801,7 +792,9 @@ tuplesubscript(PyTupleObject* self, PyObject* item)
801792
return (PyObject *)self;
802793
}
803794
else {
804-
PyTupleObject* result = tuple_alloc(slicelength);
795+
PyInterpreterState *interp = _PyInterpreterState_GET();
796+
struct _Py_tuple_state *state = &interp->tuple;
797+
PyTupleObject* result = tuple_alloc(state, slicelength);
805798
if (!result) return NULL;
806799

807800
src = self->ob_item;
@@ -963,13 +956,14 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize)
963956
}
964957

965958
void
966-
_PyTuple_ClearFreeList(void)
959+
_PyTuple_ClearFreeList(PyThreadState *tstate)
967960
{
968961
#if PyTuple_MAXSAVESIZE > 0
962+
struct _Py_tuple_state *state = &tstate->interp->tuple;
969963
for (Py_ssize_t i = 1; i < PyTuple_MAXSAVESIZE; i++) {
970-
PyTupleObject *p = free_list[i];
971-
free_list[i] = NULL;
972-
numfree[i] = 0;
964+
PyTupleObject *p = state->free_list[i];
965+
state->free_list[i] = NULL;
966+
state->numfree[i] = 0;
973967
while (p) {
974968
PyTupleObject *q = p;
975969
p = (PyTupleObject *)(p->ob_item[0]);
@@ -981,14 +975,15 @@ _PyTuple_ClearFreeList(void)
981975
}
982976

983977
void
984-
_PyTuple_Fini(void)
978+
_PyTuple_Fini(PyThreadState *tstate)
985979
{
986980
#if PyTuple_MAXSAVESIZE > 0
981+
struct _Py_tuple_state *state = &tstate->interp->tuple;
987982
/* empty tuples are used all over the place and applications may
988983
* rely on the fact that an empty tuple is a singleton. */
989-
Py_CLEAR(free_list[0]);
984+
Py_CLEAR(state->free_list[0]);
990985

991-
_PyTuple_ClearFreeList();
986+
_PyTuple_ClearFreeList(tstate);
992987
#endif
993988
}
994989

Python/pylifecycle.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1252,7 +1252,9 @@ finalize_interp_types(PyThreadState *tstate, int is_main_interp)
12521252
if (is_main_interp) {
12531253
/* Sundry finalizers */
12541254
_PyFrame_Fini();
1255-
_PyTuple_Fini();
1255+
}
1256+
_PyTuple_Fini(tstate);
1257+
if (is_main_interp) {
12561258
_PyList_Fini();
12571259
_PySet_Fini();
12581260
_PyBytes_Fini();

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy