Skip to content

Commit 35d28d1

Browse files
vstinnerarun-mani-j
authored andcommitted
bpo-40521: Make dict free lists per-interpreter (pythonGH-20645)
Each interpreter now has its own dict free list: * Move dict free lists into PyInterpreterState. * Move PyDict_MAXFREELIST define to pycore_interp.h * Add _Py_dict_state structure. * Add tstate parameter to _PyDict_ClearFreeList() and _PyDict_Fini(). * In debug mode, ensure that the dict free lists are not used after _PyDict_Fini() is called. * Remove "#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS".
1 parent b07ee97 commit 35d28d1

File tree

7 files changed

+98
-81
lines changed

7 files changed

+98
-81
lines changed

Include/internal/pycore_gc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ extern void _PyFrame_ClearFreeList(PyThreadState *tstate);
169169
extern void _PyTuple_ClearFreeList(PyThreadState *tstate);
170170
extern void _PyFloat_ClearFreeList(PyThreadState *tstate);
171171
extern void _PyList_ClearFreeList(PyThreadState *tstate);
172-
extern void _PyDict_ClearFreeList(void);
172+
extern void _PyDict_ClearFreeList(PyThreadState *tstate);
173173
extern void _PyAsyncGen_ClearFreeLists(PyThreadState *tstate);
174174
extern void _PyContext_ClearFreeList(PyThreadState *tstate);
175175

Include/internal/pycore_interp.h

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ struct _Py_unicode_state {
6969
struct _Py_unicode_fs_codec fs_codec;
7070
};
7171

72+
struct _Py_float_state {
73+
/* Special free list
74+
free_list is a singly-linked list of available PyFloatObjects,
75+
linked via abuse of their ob_type members. */
76+
int numfree;
77+
PyFloatObject *free_list;
78+
};
79+
7280
/* Speed optimization to avoid frequent malloc/free of small tuples */
7381
#ifndef PyTuple_MAXSAVESIZE
7482
// Largest tuple to save on free list
@@ -99,12 +107,16 @@ struct _Py_list_state {
99107
int numfree;
100108
};
101109

102-
struct _Py_float_state {
103-
/* Special free list
104-
free_list is a singly-linked list of available PyFloatObjects,
105-
linked via abuse of their ob_type members. */
110+
#ifndef PyDict_MAXFREELIST
111+
# define PyDict_MAXFREELIST 80
112+
#endif
113+
114+
struct _Py_dict_state {
115+
/* Dictionary reuse scheme to save calls to malloc and free */
116+
PyDictObject *free_list[PyDict_MAXFREELIST];
106117
int numfree;
107-
PyFloatObject *free_list;
118+
PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST];
119+
int keys_numfree;
108120
};
109121

110122
struct _Py_frame_state {
@@ -136,7 +148,6 @@ struct _Py_context_state {
136148
};
137149

138150

139-
140151
/* interpreter state */
141152

142153
#define _PY_NSMALLPOSINTS 257
@@ -182,8 +193,6 @@ struct _is {
182193
PyObject *codec_error_registry;
183194
int codecs_initialized;
184195

185-
struct _Py_unicode_state unicode;
186-
187196
PyConfig config;
188197
#ifdef HAVE_DLOPEN
189198
int dlopenflags;
@@ -224,16 +233,18 @@ struct _is {
224233
*/
225234
PyLongObject* small_ints[_PY_NSMALLNEGINTS + _PY_NSMALLPOSINTS];
226235
#endif
236+
struct _Py_unicode_state unicode;
237+
struct _Py_float_state float_state;
238+
/* Using a cache is very effective since typically only a single slice is
239+
created and then deleted again. */
240+
PySliceObject *slice_cache;
241+
227242
struct _Py_tuple_state tuple;
228243
struct _Py_list_state list;
229-
struct _Py_float_state float_state;
244+
struct _Py_dict_state dict_state;
230245
struct _Py_frame_state frame;
231246
struct _Py_async_gen_state async_gen;
232247
struct _Py_context_state context;
233-
234-
/* Using a cache is very effective since typically only a single slice is
235-
created and then deleted again. */
236-
PySliceObject *slice_cache;
237248
};
238249

239250
/* Used by _PyImport_Cleanup() */

Include/internal/pycore_pylifecycle.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ extern PyStatus _PyGC_Init(PyThreadState *tstate);
5959
/* Various internal finalizers */
6060

6161
extern void _PyFrame_Fini(PyThreadState *tstate);
62-
extern void _PyDict_Fini(void);
62+
extern void _PyDict_Fini(PyThreadState *tstate);
6363
extern void _PyTuple_Fini(PyThreadState *tstate);
6464
extern void _PyList_Fini(PyThreadState *tstate);
6565
extern void _PySet_Fini(void);
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
The tuple free lists, the empty tuple singleton, the list free list, the float
2-
free list, the slice cache, the frame free list, the asynchronous generator
3-
free lists, and the context free list are no longer shared by all interpreters:
4-
each interpreter now its has own free lists and caches.
2+
free list, the slice cache, the dict free lists, the frame free list, the
3+
asynchronous generator free lists, and the context free list are no longer
4+
shared by all interpreters: each interpreter now its has own free lists and
5+
caches.

Modules/gcmodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,7 @@ clear_freelists(PyThreadState *tstate)
10381038
_PyTuple_ClearFreeList(tstate);
10391039
_PyFloat_ClearFreeList(tstate);
10401040
_PyList_ClearFreeList(tstate);
1041-
_PyDict_ClearFreeList();
1041+
_PyDict_ClearFreeList(tstate);
10421042
_PyAsyncGen_ClearFreeLists(tstate);
10431043
_PyContext_ClearFreeList(tstate);
10441044
}

Objects/dictobject.c

Lines changed: 66 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -247,58 +247,47 @@ static uint64_t pydict_global_version = 0;
247247

248248
#define DICT_NEXT_VERSION() (++pydict_global_version)
249249

250-
/* Dictionary reuse scheme to save calls to malloc and free */
251-
#ifndef PyDict_MAXFREELIST
252-
#define PyDict_MAXFREELIST 80
253-
#endif
254-
255-
/* bpo-40521: dict free lists are shared by all interpreters. */
256-
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
257-
# undef PyDict_MAXFREELIST
258-
# define PyDict_MAXFREELIST 0
259-
#endif
260-
261-
#if PyDict_MAXFREELIST > 0
262-
static PyDictObject *free_list[PyDict_MAXFREELIST];
263-
static int numfree = 0;
264-
static PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST];
265-
static int numfreekeys = 0;
266-
#endif
267-
268250
#include "clinic/dictobject.c.h"
269251

270252
void
271-
_PyDict_ClearFreeList(void)
253+
_PyDict_ClearFreeList(PyThreadState *tstate)
272254
{
273-
#if PyDict_MAXFREELIST > 0
274-
while (numfree) {
275-
PyDictObject *op = free_list[--numfree];
255+
struct _Py_dict_state *state = &tstate->interp->dict_state;
256+
while (state->numfree) {
257+
PyDictObject *op = state->free_list[--state->numfree];
276258
assert(PyDict_CheckExact(op));
277259
PyObject_GC_Del(op);
278260
}
279-
while (numfreekeys) {
280-
PyObject_FREE(keys_free_list[--numfreekeys]);
261+
while (state->keys_numfree) {
262+
PyObject_FREE(state->keys_free_list[--state->keys_numfree]);
281263
}
282-
#endif
283264
}
284265

285-
/* Print summary info about the state of the optimized allocator */
266+
286267
void
287-
_PyDict_DebugMallocStats(FILE *out)
268+
_PyDict_Fini(PyThreadState *tstate)
288269
{
289-
#if PyDict_MAXFREELIST > 0
290-
_PyDebugAllocatorStats(out,
291-
"free PyDictObject", numfree, sizeof(PyDictObject));
270+
_PyDict_ClearFreeList(tstate);
271+
#ifdef Py_DEBUG
272+
PyInterpreterState *interp = _PyInterpreterState_GET();
273+
struct _Py_dict_state *state = &interp->dict_state;
274+
state->numfree = -1;
275+
state->keys_numfree = -1;
292276
#endif
293277
}
294278

295279

280+
/* Print summary info about the state of the optimized allocator */
296281
void
297-
_PyDict_Fini(void)
282+
_PyDict_DebugMallocStats(FILE *out)
298283
{
299-
_PyDict_ClearFreeList();
284+
PyInterpreterState *interp = _PyInterpreterState_GET();
285+
struct _Py_dict_state *state = &interp->dict_state;
286+
_PyDebugAllocatorStats(out, "free PyDictObject",
287+
state->numfree, sizeof(PyDictObject));
300288
}
301289

290+
302291
#define DK_SIZE(dk) ((dk)->dk_size)
303292
#if SIZEOF_VOID_P > 4
304293
#define DK_IXSIZE(dk) \
@@ -543,7 +532,8 @@ _PyDict_CheckConsistency(PyObject *op, int check_content)
543532
}
544533

545534

546-
static PyDictKeysObject *new_keys_object(Py_ssize_t size)
535+
static PyDictKeysObject*
536+
new_keys_object(Py_ssize_t size)
547537
{
548538
PyDictKeysObject *dk;
549539
Py_ssize_t es, usable;
@@ -567,12 +557,16 @@ static PyDictKeysObject *new_keys_object(Py_ssize_t size)
567557
es = sizeof(Py_ssize_t);
568558
}
569559

570-
#if PyDict_MAXFREELIST > 0
571-
if (size == PyDict_MINSIZE && numfreekeys > 0) {
572-
dk = keys_free_list[--numfreekeys];
560+
PyInterpreterState *interp = _PyInterpreterState_GET();
561+
struct _Py_dict_state *state = &interp->dict_state;
562+
#ifdef Py_DEBUG
563+
// new_keys_object() must not be called after _PyDict_Fini()
564+
assert(state->keys_numfree != -1);
565+
#endif
566+
if (size == PyDict_MINSIZE && state->keys_numfree > 0) {
567+
dk = state->keys_free_list[--state->keys_numfree];
573568
}
574569
else
575-
#endif
576570
{
577571
dk = PyObject_MALLOC(sizeof(PyDictKeysObject)
578572
+ es * size
@@ -604,12 +598,16 @@ free_keys_object(PyDictKeysObject *keys)
604598
Py_XDECREF(entries[i].me_key);
605599
Py_XDECREF(entries[i].me_value);
606600
}
607-
#if PyDict_MAXFREELIST > 0
608-
if (keys->dk_size == PyDict_MINSIZE && numfreekeys < PyDict_MAXFREELIST) {
609-
keys_free_list[numfreekeys++] = keys;
601+
PyInterpreterState *interp = _PyInterpreterState_GET();
602+
struct _Py_dict_state *state = &interp->dict_state;
603+
#ifdef Py_DEBUG
604+
// free_keys_object() must not be called after _PyDict_Fini()
605+
assert(state->keys_numfree != -1);
606+
#endif
607+
if (keys->dk_size == PyDict_MINSIZE && state->keys_numfree < PyDict_MAXFREELIST) {
608+
state->keys_free_list[state->keys_numfree++] = keys;
610609
return;
611610
}
612-
#endif
613611
PyObject_FREE(keys);
614612
}
615613

@@ -622,16 +620,19 @@ new_dict(PyDictKeysObject *keys, PyObject **values)
622620
{
623621
PyDictObject *mp;
624622
assert(keys != NULL);
625-
#if PyDict_MAXFREELIST > 0
626-
if (numfree) {
627-
mp = free_list[--numfree];
623+
PyInterpreterState *interp = _PyInterpreterState_GET();
624+
struct _Py_dict_state *state = &interp->dict_state;
625+
#ifdef Py_DEBUG
626+
// new_dict() must not be called after _PyDict_Fini()
627+
assert(state->numfree != -1);
628+
#endif
629+
if (state->numfree) {
630+
mp = state->free_list[--state->numfree];
628631
assert (mp != NULL);
629632
assert (Py_IS_TYPE(mp, &PyDict_Type));
630633
_Py_NewReference((PyObject *)mp);
631634
}
632-
else
633-
#endif
634-
{
635+
else {
635636
mp = PyObject_GC_New(PyDictObject, &PyDict_Type);
636637
if (mp == NULL) {
637638
dictkeys_decref(keys);
@@ -1280,15 +1281,18 @@ dictresize(PyDictObject *mp, Py_ssize_t minsize)
12801281
#ifdef Py_REF_DEBUG
12811282
_Py_RefTotal--;
12821283
#endif
1283-
#if PyDict_MAXFREELIST > 0
1284+
PyInterpreterState *interp = _PyInterpreterState_GET();
1285+
struct _Py_dict_state *state = &interp->dict_state;
1286+
#ifdef Py_DEBUG
1287+
// dictresize() must not be called after _PyDict_Fini()
1288+
assert(state->keys_numfree != -1);
1289+
#endif
12841290
if (oldkeys->dk_size == PyDict_MINSIZE &&
1285-
numfreekeys < PyDict_MAXFREELIST)
1291+
state->keys_numfree < PyDict_MAXFREELIST)
12861292
{
1287-
keys_free_list[numfreekeys++] = oldkeys;
1293+
state->keys_free_list[state->keys_numfree++] = oldkeys;
12881294
}
1289-
else
1290-
#endif
1291-
{
1295+
else {
12921296
PyObject_FREE(oldkeys);
12931297
}
12941298
}
@@ -2028,13 +2032,16 @@ dict_dealloc(PyDictObject *mp)
20282032
assert(keys->dk_refcnt == 1);
20292033
dictkeys_decref(keys);
20302034
}
2031-
#if PyDict_MAXFREELIST > 0
2032-
if (numfree < PyDict_MAXFREELIST && Py_IS_TYPE(mp, &PyDict_Type)) {
2033-
free_list[numfree++] = mp;
2034-
}
2035-
else
2035+
PyInterpreterState *interp = _PyInterpreterState_GET();
2036+
struct _Py_dict_state *state = &interp->dict_state;
2037+
#ifdef Py_DEBUG
2038+
// new_dict() must not be called after _PyDict_Fini()
2039+
assert(state->numfree != -1);
20362040
#endif
2037-
{
2041+
if (state->numfree < PyDict_MAXFREELIST && Py_IS_TYPE(mp, &PyDict_Type)) {
2042+
state->free_list[state->numfree++] = mp;
2043+
}
2044+
else {
20382045
Py_TYPE(mp)->tp_free((PyObject *)mp);
20392046
}
20402047
Py_TRASHCAN_END

Python/pylifecycle.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,9 +1258,7 @@ finalize_interp_types(PyThreadState *tstate, int is_main_interp)
12581258
if (is_main_interp) {
12591259
_PySet_Fini();
12601260
}
1261-
if (is_main_interp) {
1262-
_PyDict_Fini();
1263-
}
1261+
_PyDict_Fini(tstate);
12641262
_PyList_Fini(tstate);
12651263
_PyTuple_Fini(tstate);
12661264

0 commit comments

Comments
 (0)