Skip to content

gh-111968: Introduce _PyFreeListState and _PyFreeListState_GET API #113584

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 54 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
cd4863c
gh-111968: Introduce _Py_freelist_state and _PyFreeListState_GET API
corona10 Dec 30, 2023
333873d
nit
corona10 Dec 30, 2023
8b7e261
Remove comment
corona10 Dec 30, 2023
dd0afa2
Update _PyList_Fini
corona10 Dec 30, 2023
f0d55de
nit refactor
corona10 Dec 31, 2023
5e8e3b4
fix
corona10 Dec 31, 2023
7aa956a
Update finalize step
corona10 Dec 31, 2023
908ef13
pep7
corona10 Dec 31, 2023
0096383
Update
corona10 Jan 2, 2024
bc8dd4a
Update
corona10 Jan 2, 2024
40bad6b
fix
corona10 Jan 2, 2024
1b0f370
fix
corona10 Jan 2, 2024
3d6042e
fix
corona10 Jan 2, 2024
09403db
fix
corona10 Jan 2, 2024
c05491e
Update
corona10 Jan 2, 2024
d5f9559
fix
corona10 Jan 2, 2024
3dd3c3f
update
corona10 Jan 2, 2024
0d8ea53
nit
corona10 Jan 2, 2024
e9d8138
Address code review
corona10 Jan 2, 2024
07e0536
Update Python/pylifecycle.c
corona10 Jan 2, 2024
6c8ba67
Address code review
corona10 Jan 2, 2024
8a80970
Address code review
corona10 Jan 2, 2024
6d2f2d4
nit
corona10 Jan 2, 2024
887bacf
Use _Py_FinalizeFreeLists
corona10 Jan 2, 2024
e65952f
nit
corona10 Jan 2, 2024
25a6714
fix
corona10 Jan 2, 2024
3fd9baa
fix
corona10 Jan 2, 2024
e175b61
fix
corona10 Jan 2, 2024
295a292
nit
corona10 Jan 2, 2024
5836c4b
nit
corona10 Jan 2, 2024
7cb261f
nit
corona10 Jan 2, 2024
01bbc7a
Revert naming of clear_all_freelists
corona10 Jan 2, 2024
b08f65f
Use _Py_ClearFreeLists as possible
corona10 Jan 2, 2024
65cedee
nit
corona10 Jan 2, 2024
9a40708
Fix finalize code
corona10 Jan 2, 2024
af86a20
nit
corona10 Jan 2, 2024
4e84130
Merge remote-tracking branch 'upstream/main' into gh-111968
corona10 Jan 5, 2024
7ffa64c
Split implementation
corona10 Jan 5, 2024
d6a2feb
Add files
corona10 Jan 5, 2024
458aadb
nit
corona10 Jan 5, 2024
b105bda
nit
corona10 Jan 5, 2024
18e9216
fix
corona10 Jan 5, 2024
5bb8d3f
nit
corona10 Jan 5, 2024
2811a12
fix
corona10 Jan 6, 2024
a5f494d
fix compiler warn
corona10 Jan 6, 2024
837ae60
nit
corona10 Jan 6, 2024
08c8613
fix
corona10 Jan 6, 2024
b5eb472
Adjust comment
corona10 Jan 6, 2024
0d8dc3d
2 tabs, not 8 spaces
erlend-aasland Jan 6, 2024
fe859e1
Rename to _PyGC_ClearAllFreeLists
corona10 Jan 9, 2024
c284061
Pass is_finalization to _PyList_ClearFreeList
corona10 Jan 9, 2024
a8e39b3
fix
corona10 Jan 9, 2024
99ac375
fix
corona10 Jan 9, 2024
0c331c7
fix
corona10 Jan 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Include/internal/pycore_gc.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ extern Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate);
// Functions to clear types free lists
extern void _PyTuple_ClearFreeList(PyInterpreterState *interp);
extern void _PyFloat_ClearFreeList(PyInterpreterState *interp);
extern void _PyList_ClearFreeList(PyInterpreterState *interp);
extern void _PyList_ClearFreeList(PyFreeListState *state);
extern void _PyDict_ClearFreeList(PyInterpreterState *interp);
extern void _PyAsyncGen_ClearFreeLists(PyInterpreterState *interp);
extern void _PyContext_ClearFreeList(PyInterpreterState *interp);
Expand Down
4 changes: 3 additions & 1 deletion Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ struct _is {
// One bit is set for each non-NULL entry in code_watchers
uint8_t active_code_watchers;

#if !defined(Py_GIL_DISABLED)
struct _Py_freelist_state freelist_state;
#endif
struct _py_object_state object_state;
struct _Py_unicode_state unicode;
struct _Py_float_state float_state;
Expand All @@ -185,7 +188,6 @@ struct _is {
PySliceObject *slice_cache;

struct _Py_tuple_state tuple;
struct _Py_list_state list;
struct _Py_dict_state dict_state;
struct _Py_async_gen_state async_gen;
struct _Py_context_state context;
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ extern void _PyList_DebugMallocStats(FILE *out);

/* runtime lifecycle */

extern void _PyList_Fini(PyInterpreterState *);
extern void _PyList_Fini(PyFreeListState *);


/* other API */
Expand Down
14 changes: 14 additions & 0 deletions Include/internal/pycore_pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extern "C" {
#endif

#include "pycore_runtime.h" // _PyRuntime
#include "pycore_tstate.h" // _PyThreadStateImpl


// Values for PyThreadState.state. A thread must be in the "attached" state
Expand Down Expand Up @@ -239,6 +240,19 @@ PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void);
// See also PyInterpreterState_Get() and _PyInterpreterState_GET().
extern PyInterpreterState* _PyGILState_GetInterpreterStateUnsafe(void);

static inline PyFreeListState* _PyFreeListState_GET(void) {
PyThreadState *tstate = _PyThreadState_GET();
#ifdef Py_DEBUG
_Py_EnsureTstateNotNULL(tstate);
#endif

#ifdef Py_GIL_DISABLED
return &((_PyThreadStateImpl*)tstate)->freelist_state;
#else
return &tstate->interp->freelist_state;
#endif
}

#ifdef __cplusplus
}
#endif
Expand Down
5 changes: 5 additions & 0 deletions Include/internal/pycore_tstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ extern "C" {
#include "pycore_mimalloc.h" // struct _mimalloc_thread_state


typedef struct _Py_freelist_state {
struct _Py_list_state list;
} _Py_freelist_state;

// Every PyThreadState is actually allocated as a _PyThreadStateImpl. The
// PyThreadState fields are exposed as part of the C API, although most fields
// are intended to be private. The _PyThreadStateImpl fields not exposed.
Expand All @@ -20,6 +24,7 @@ typedef struct _PyThreadStateImpl {

#ifdef Py_GIL_DISABLED
struct _mimalloc_thread_state mimalloc;
struct _Py_freelist_state freelist_state;
#endif

} _PyThreadStateImpl;
Expand Down
1 change: 1 addition & 0 deletions Include/pytypedefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ typedef struct _frame PyFrameObject;

typedef struct _ts PyThreadState;
typedef struct _is PyInterpreterState;
typedef struct _Py_freelist_state PyFreeListState;

#ifdef __cplusplus
}
Expand Down
24 changes: 21 additions & 3 deletions Modules/gcmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1062,20 +1062,38 @@ delete_garbage(PyThreadState *tstate, GCState *gcstate,
}
}

static void
clear_freelists(PyFreeListState *state)
{
Copy link
Member Author

@corona10 corona10 Dec 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following functions will be moved here in the end.

  • _PyTuple_ClearFreeList
  • _PyFloat_ClearFreeList
  • _PyDict_ClearFreeList
  • _PyAsyncGen_ClearFreeLists
  • _PyContext_ClearFreeList

_PyList_ClearFreeList(state);
}

/* Clear all free lists
* All free lists are cleared during the collection of the highest generation.
* Allocated items in the free list may keep a pymalloc arena occupied.
* Clearing the free lists may give back memory to the OS earlier.
*/
static void
clear_freelists(PyInterpreterState *interp)
clear_all_freelists(PyInterpreterState *interp)
{
_PyTuple_ClearFreeList(interp);
_PyFloat_ClearFreeList(interp);
_PyList_ClearFreeList(interp);
_PyDict_ClearFreeList(interp);
_PyAsyncGen_ClearFreeLists(interp);
_PyContext_ClearFreeList(interp);
#if defined(Py_GIL_DISABLED)
HEAD_LOCK(&_PyRuntime);
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)interp->threads.head;
while (tstate != NULL) {
clear_freelists(&tstate->freelist_state);
tstate = (_PyThreadStateImpl *)tstate->base.next;
}
HEAD_UNLOCK(&_PyRuntime);
#else
// Only free-lists per interpreter are existed.
PyFreeListState *state = _PyFreeListState_GET();
clear_freelists(state);
#endif
}

// Show stats for objects in each generations
Expand Down Expand Up @@ -1485,7 +1503,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
/* Clear free list only during the collection of the highest
* generation */
if (generation == NUM_GENERATIONS-1) {
clear_freelists(tstate->interp);
clear_all_freelists(tstate->interp);
}

if (_PyErr_Occurred(tstate)) {
Expand Down
20 changes: 10 additions & 10 deletions Objects/listobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ _Py_DECLARE_STR(list_err, "list index out of range");
static struct _Py_list_state *
get_list_state(void)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
return &interp->list;
PyFreeListState *state = _PyFreeListState_GET();
assert(state != NULL);
return &state->list;
}
#endif

Expand Down Expand Up @@ -120,25 +121,24 @@ list_preallocate_exact(PyListObject *self, Py_ssize_t size)
}

void
_PyList_ClearFreeList(PyInterpreterState *interp)
_PyList_ClearFreeList(PyFreeListState *state)
{
#if PyList_MAXFREELIST > 0
struct _Py_list_state *state = &interp->list;
while (state->numfree) {
PyListObject *op = state->free_list[--state->numfree];
struct _Py_list_state *list_state = &state->list;
while (list_state->numfree) {
PyListObject *op = list_state->free_list[--list_state->numfree];
assert(PyList_CheckExact(op));
PyObject_GC_Del(op);
}
#endif
}

void
_PyList_Fini(PyInterpreterState *interp)
_PyList_Fini(PyFreeListState *state)
{
_PyList_ClearFreeList(interp);
_PyList_ClearFreeList(state);
#if defined(Py_DEBUG) && PyList_MAXFREELIST > 0
struct _Py_list_state *state = &interp->list;
state->numfree = -1;
state->list.numfree = -1;
#endif
}

Expand Down
20 changes: 19 additions & 1 deletion Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -1727,6 +1727,11 @@ flush_std_files(void)

*/

static void
finalize_free_lists(PyFreeListState *state)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following functions will be moved here in the end.

  • _PyDict_Fini
  • _PyTuple_Fini
  • _PySlice_Fini
  • _PyFloat_Fini
  • _PyContext_Fini

{
_PyList_Fini(state);
}

static void
finalize_interp_types(PyInterpreterState *interp)
Expand All @@ -1752,13 +1757,26 @@ finalize_interp_types(PyInterpreterState *interp)
_PyUnicode_ClearInterned(interp);

_PyDict_Fini(interp);
_PyList_Fini(interp);
_PyTuple_Fini(interp);

_PySlice_Fini(interp);

_PyUnicode_Fini(interp);
_PyFloat_Fini(interp);

#if defined(Py_GIL_DISABLED)
HEAD_LOCK(&_PyRuntime);
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)interp->threads.head;
while (tstate != NULL) {
finalize_free_lists(&tstate->freelist_state);
tstate = (_PyThreadStateImpl *)tstate->base.next;
}
HEAD_UNLOCK(&_PyRuntime);
#else
PyFreeListState *state = _PyFreeListState_GET();
finalize_free_lists(state);
#endif

#ifdef Py_DEBUG
_PyStaticObjects_CheckRefcnt(interp);
#endif
Expand Down