bpo-40521: Make dict free lists per-interpreter (GH-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".
This commit is contained in:
Victor Stinner 2020-06-23 11:33:18 +02:00 committed by GitHub
parent 26a1ad1c24
commit b4e85cadfb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 102 additions and 85 deletions

View file

@ -247,58 +247,47 @@ static uint64_t pydict_global_version = 0;
#define DICT_NEXT_VERSION() (++pydict_global_version)
/* Dictionary reuse scheme to save calls to malloc and free */
#ifndef PyDict_MAXFREELIST
#define PyDict_MAXFREELIST 80
#endif
/* bpo-40521: dict free lists are shared by all interpreters. */
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
# undef PyDict_MAXFREELIST
# define PyDict_MAXFREELIST 0
#endif
#if PyDict_MAXFREELIST > 0
static PyDictObject *free_list[PyDict_MAXFREELIST];
static int numfree = 0;
static PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST];
static int numfreekeys = 0;
#endif
#include "clinic/dictobject.c.h"
void
_PyDict_ClearFreeList(void)
_PyDict_ClearFreeList(PyThreadState *tstate)
{
#if PyDict_MAXFREELIST > 0
while (numfree) {
PyDictObject *op = free_list[--numfree];
struct _Py_dict_state *state = &tstate->interp->dict_state;
while (state->numfree) {
PyDictObject *op = state->free_list[--state->numfree];
assert(PyDict_CheckExact(op));
PyObject_GC_Del(op);
}
while (numfreekeys) {
PyObject_FREE(keys_free_list[--numfreekeys]);
while (state->keys_numfree) {
PyObject_FREE(state->keys_free_list[--state->keys_numfree]);
}
}
void
_PyDict_Fini(PyThreadState *tstate)
{
_PyDict_ClearFreeList(tstate);
#ifdef Py_DEBUG
PyInterpreterState *interp = _PyInterpreterState_GET();
struct _Py_dict_state *state = &interp->dict_state;
state->numfree = -1;
state->keys_numfree = -1;
#endif
}
/* Print summary info about the state of the optimized allocator */
void
_PyDict_DebugMallocStats(FILE *out)
{
#if PyDict_MAXFREELIST > 0
_PyDebugAllocatorStats(out,
"free PyDictObject", numfree, sizeof(PyDictObject));
#endif
PyInterpreterState *interp = _PyInterpreterState_GET();
struct _Py_dict_state *state = &interp->dict_state;
_PyDebugAllocatorStats(out, "free PyDictObject",
state->numfree, sizeof(PyDictObject));
}
void
_PyDict_Fini(void)
{
_PyDict_ClearFreeList();
}
#define DK_SIZE(dk) ((dk)->dk_size)
#if SIZEOF_VOID_P > 4
#define DK_IXSIZE(dk) \
@ -543,7 +532,8 @@ _PyDict_CheckConsistency(PyObject *op, int check_content)
}
static PyDictKeysObject *new_keys_object(Py_ssize_t size)
static PyDictKeysObject*
new_keys_object(Py_ssize_t size)
{
PyDictKeysObject *dk;
Py_ssize_t es, usable;
@ -567,12 +557,16 @@ static PyDictKeysObject *new_keys_object(Py_ssize_t size)
es = sizeof(Py_ssize_t);
}
#if PyDict_MAXFREELIST > 0
if (size == PyDict_MINSIZE && numfreekeys > 0) {
dk = keys_free_list[--numfreekeys];
PyInterpreterState *interp = _PyInterpreterState_GET();
struct _Py_dict_state *state = &interp->dict_state;
#ifdef Py_DEBUG
// new_keys_object() must not be called after _PyDict_Fini()
assert(state->keys_numfree != -1);
#endif
if (size == PyDict_MINSIZE && state->keys_numfree > 0) {
dk = state->keys_free_list[--state->keys_numfree];
}
else
#endif
{
dk = PyObject_MALLOC(sizeof(PyDictKeysObject)
+ es * size
@ -604,12 +598,16 @@ free_keys_object(PyDictKeysObject *keys)
Py_XDECREF(entries[i].me_key);
Py_XDECREF(entries[i].me_value);
}
#if PyDict_MAXFREELIST > 0
if (keys->dk_size == PyDict_MINSIZE && numfreekeys < PyDict_MAXFREELIST) {
keys_free_list[numfreekeys++] = keys;
PyInterpreterState *interp = _PyInterpreterState_GET();
struct _Py_dict_state *state = &interp->dict_state;
#ifdef Py_DEBUG
// free_keys_object() must not be called after _PyDict_Fini()
assert(state->keys_numfree != -1);
#endif
if (keys->dk_size == PyDict_MINSIZE && state->keys_numfree < PyDict_MAXFREELIST) {
state->keys_free_list[state->keys_numfree++] = keys;
return;
}
#endif
PyObject_FREE(keys);
}
@ -622,16 +620,19 @@ new_dict(PyDictKeysObject *keys, PyObject **values)
{
PyDictObject *mp;
assert(keys != NULL);
#if PyDict_MAXFREELIST > 0
if (numfree) {
mp = free_list[--numfree];
PyInterpreterState *interp = _PyInterpreterState_GET();
struct _Py_dict_state *state = &interp->dict_state;
#ifdef Py_DEBUG
// new_dict() must not be called after _PyDict_Fini()
assert(state->numfree != -1);
#endif
if (state->numfree) {
mp = state->free_list[--state->numfree];
assert (mp != NULL);
assert (Py_IS_TYPE(mp, &PyDict_Type));
_Py_NewReference((PyObject *)mp);
}
else
#endif
{
else {
mp = PyObject_GC_New(PyDictObject, &PyDict_Type);
if (mp == NULL) {
dictkeys_decref(keys);
@ -1280,15 +1281,18 @@ dictresize(PyDictObject *mp, Py_ssize_t minsize)
#ifdef Py_REF_DEBUG
_Py_RefTotal--;
#endif
#if PyDict_MAXFREELIST > 0
if (oldkeys->dk_size == PyDict_MINSIZE &&
numfreekeys < PyDict_MAXFREELIST)
{
keys_free_list[numfreekeys++] = oldkeys;
}
else
PyInterpreterState *interp = _PyInterpreterState_GET();
struct _Py_dict_state *state = &interp->dict_state;
#ifdef Py_DEBUG
// dictresize() must not be called after _PyDict_Fini()
assert(state->keys_numfree != -1);
#endif
if (oldkeys->dk_size == PyDict_MINSIZE &&
state->keys_numfree < PyDict_MAXFREELIST)
{
state->keys_free_list[state->keys_numfree++] = oldkeys;
}
else {
PyObject_FREE(oldkeys);
}
}
@ -2028,13 +2032,16 @@ dict_dealloc(PyDictObject *mp)
assert(keys->dk_refcnt == 1);
dictkeys_decref(keys);
}
#if PyDict_MAXFREELIST > 0
if (numfree < PyDict_MAXFREELIST && Py_IS_TYPE(mp, &PyDict_Type)) {
free_list[numfree++] = mp;
}
else
PyInterpreterState *interp = _PyInterpreterState_GET();
struct _Py_dict_state *state = &interp->dict_state;
#ifdef Py_DEBUG
// new_dict() must not be called after _PyDict_Fini()
assert(state->numfree != -1);
#endif
{
if (state->numfree < PyDict_MAXFREELIST && Py_IS_TYPE(mp, &PyDict_Type)) {
state->free_list[state->numfree++] = mp;
}
else {
Py_TYPE(mp)->tp_free((PyObject *)mp);
}
Py_TRASHCAN_END