Skip to content

bpo-45953: Statically allocate the main interpreter (and initial thread state). #29883

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 78 commits into from
Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
5e6eda9
Return void from _PyEval_InitState().
ericsnowcurrently Nov 18, 2021
de12b98
Fix a memory leak.
ericsnowcurrently Nov 18, 2021
035eff3
Move thread-related interpreter state into a sub-struct.
ericsnowcurrently Nov 17, 2021
1569a53
Factor out init_threadstate().
ericsnowcurrently Nov 18, 2021
518e3f3
Do not initialize a thread state if already initialized.
ericsnowcurrently Nov 18, 2021
9995a84
Avoid allocating the first thread state.
ericsnowcurrently Nov 18, 2021
f22d0da
Factor out init_interpreter().
ericsnowcurrently Nov 18, 2021
cf39d57
Preallocate the main interpreter.
ericsnowcurrently Nov 18, 2021
b840213
Add a note about expectations for init_interpreter().
ericsnowcurrently Dec 1, 2021
3bf69b0
Add a NEWS entry.
ericsnowcurrently Dec 1, 2021
4de466e
Add _PyInterpreterState_Main().
ericsnowcurrently Dec 1, 2021
9bb32a6
Fix a check for the main interpreter.
ericsnowcurrently Dec 2, 2021
385f298
Ensure no subinterpreters are created until after the main interprete…
ericsnowcurrently Dec 2, 2021
354997f
Make sure the interpreter is fully initialized.
ericsnowcurrently Dec 2, 2021
9bbf8a0
Use PyMem_RawCalloc() to allocate thread states.
ericsnowcurrently Dec 2, 2021
8d1cf35
Make sure the thread state is fully initialized.
ericsnowcurrently Dec 2, 2021
188aed8
Clear the preallocated states when "deleted".
ericsnowcurrently Dec 2, 2021
e0da483
Statically initialize the "preallocated" states.
ericsnowcurrently Dec 2, 2021
46c7767
Fix a typo.
ericsnowcurrently Dec 3, 2021
7ca8d1c
Stop memset'ing the statically initialized _PyRuntime.
ericsnowcurrently Dec 3, 2021
ed6c273
Fix typos.
ericsnowcurrently Dec 3, 2021
29e1764
Only expose _PyThreadState_INIT in the internal API.
ericsnowcurrently Dec 3, 2021
1a83910
If needed, "statically" initialize the preallocated thread state.
ericsnowcurrently Dec 3, 2021
8631805
Check if _preallocated is initialized when initializing from other.
ericsnowcurrently Dec 3, 2021
a0569bb
init_interpreter_from_other -> init_interpreter_static_data.
ericsnowcurrently Dec 3, 2021
4c3c0f9
"Statically" initialize the preallocated thread state.
ericsnowcurrently Dec 3, 2021
47bfa7e
Clear _PyRuntime during finalization.
ericsnowcurrently Dec 3, 2021
dd69ffa
Track if interpreter or thread state has been initialized.
ericsnowcurrently Dec 3, 2021
ba75803
Zero-out states during re-init instead of fini.
ericsnowcurrently Dec 3, 2021
eef3194
Use Py_uintptr_t instead of ssize_t.
ericsnowcurrently Dec 3, 2021
8703297
Add Py*State._initialized.
ericsnowcurrently Dec 7, 2021
d1eddad
Factor out init_threadstate().
ericsnowcurrently Dec 7, 2021
8a8150c
Factor out free_threadstate().
ericsnowcurrently Dec 7, 2021
7a37237
Factor out init_interpreter().
ericsnowcurrently Dec 7, 2021
b2073ac
Factor out free_interpreter().
ericsnowcurrently Dec 7, 2021
21a522d
Factor out init_runtime().
ericsnowcurrently Dec 7, 2021
7b376d4
Factor out _PyRuntimeState_reset().
ericsnowcurrently Dec 7, 2021
562ee42
Merge branch 'main' into preallocate-main
ericsnowcurrently Dec 8, 2021
ed8f5cd
Add _PyInterpreterState_Main().
ericsnowcurrently Dec 1, 2021
6da97b7
Drop an outdated comment.
ericsnowcurrently Dec 8, 2021
cf24942
Fix a check for the main interpreter.
ericsnowcurrently Dec 2, 2021
fac6af6
Merge branch 'main' into preallocate-main
ericsnowcurrently Dec 8, 2021
4aa2073
Merge branch 'main' into preallocate-main
ericsnowcurrently Dec 8, 2021
f135168
Merge branch 'main' into preallocate-main
ericsnowcurrently Dec 14, 2021
2b4ba04
Move *_reset() out of the Include/.
ericsnowcurrently Dec 14, 2021
24d1de2
Pass the parent context to init_*_static_data().
ericsnowcurrently Dec 14, 2021
84ae7db
Drop reset_*() and init_*_static_data().
ericsnowcurrently Dec 14, 2021
e9de30c
Drop _Py_global_objects_reset().
ericsnowcurrently Dec 14, 2021
51a32ce
Drop _preallocated.initialized.
ericsnowcurrently Dec 16, 2021
7104518
Drop _preallocated_used.
ericsnowcurrently Dec 16, 2021
f7a8389
Drop init of PyThreadState._preallocated for now.
ericsnowcurrently Dec 16, 2021
18080a1
Note what could be pre-allocated.
ericsnowcurrently Dec 16, 2021
0457119
Add Py*State._static.
ericsnowcurrently Dec 16, 2021
923ca7a
Avoid an empty struct.
ericsnowcurrently Dec 17, 2021
99c2ae1
Drop PyInterpreterState._preallocated.initialized.
ericsnowcurrently Jan 4, 2022
3bc73ee
Drop an outdated comment.
ericsnowcurrently Jan 4, 2022
c1808ae
Drop outdated comments.
ericsnowcurrently Jan 4, 2022
7b98e59
Clarify what remains to be pre-allocated for _PyRuntimeState.
ericsnowcurrently Jan 4, 2022
0383eae
Explain what _preallocated is for.
ericsnowcurrently Jan 4, 2022
c959db0
Clarify why some data isn't pre-allocated.
ericsnowcurrently Jan 4, 2022
5b34b42
Fold _PyThreadState_INIT into _PyInterpreterState_INIT.
ericsnowcurrently Jan 10, 2022
837bfe6
Fold _PyInterpreterState_INIT into _PyRuntimeState_INIT.
ericsnowcurrently Jan 10, 2022
ebefa35
Drop PyThreadState._preallocated.
ericsnowcurrently Jan 10, 2022
eb7a080
tstate -> initial_thread.
ericsnowcurrently Jan 11, 2022
1ef4a39
Mirror the top-level structure in _preallocated.
ericsnowcurrently Jan 11, 2022
771e574
Merge branch 'main' into preallocate-main
ericsnowcurrently Jan 11, 2022
b5c43f1
Clarify some comments.
ericsnowcurrently Jan 11, 2022
bfc3906
Fold in _PyRuntimeState._preallocated.
ericsnowcurrently Jan 11, 2022
9b25c8b
Make "initial" const.
ericsnowcurrently Jan 11, 2022
e776c31
Fold in PyInterpreterState._preallocated.
ericsnowcurrently Jan 11, 2022
c6bda1f
Move the initial thread to the bottom of PyInterpreterState.
ericsnowcurrently Jan 11, 2022
8ab6261
Split up _PyRuntimeState_INIT for less nesting and clutter.
ericsnowcurrently Jan 11, 2022
ff8de28
Fix a comment.
ericsnowcurrently Jan 11, 2022
b28afb3
Move the main interpreter to the bottom of _PyRuntimeState.
ericsnowcurrently Jan 11, 2022
0d30300
Clarify some comments.
ericsnowcurrently Jan 11, 2022
e193794
Add a comment for the _static field.
ericsnowcurrently Jan 12, 2022
e9f1262
Move the comments about pre-allocated data down.
ericsnowcurrently Jan 12, 2022
a6a6471
Fix a typo.
ericsnowcurrently Jan 12, 2022
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
10 changes: 9 additions & 1 deletion Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
# error "this header file must not be included directly"
#endif

#include <stdbool.h>


PyAPI_FUNC(int) _PyInterpreterState_RequiresIDRef(PyInterpreterState *);
PyAPI_FUNC(void) _PyInterpreterState_RequireIDRef(PyInterpreterState *, int);

Expand Down Expand Up @@ -83,6 +86,9 @@ struct _ts {
after allocation. */
int _initialized;

/* Was this thread state statically allocated? */
bool _static;

int recursion_remaining;
int recursion_limit;
int recursion_headroom; /* Allow 50 more calls to handle any errors. */
Expand Down Expand Up @@ -175,9 +181,11 @@ struct _ts {
PyObject **datastack_top;
PyObject **datastack_limit;
/* XXX signal handlers should also be here */

};


/* other API */

Copy link
Member

Choose a reason for hiding this comment

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

Can it be turned into a variant below?

#define _PyThreadState_INIT { ._preallocated.initialized = 1 }

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the tip. What's the advantage?

Copy link
Member

@arhadthedev arhadthedev Dec 4, 2021

Choose a reason for hiding this comment

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

Actually, this is a minor adjustment done while it does not require a separate pull request.

More compact declaration is easier to grasp in a single eye swipe. Like _PyThreadState_INIT is the preallocated's initialized set to one versus _PyThreadState_INIT is... the preallocated's initialized set to one... ah, that's it. In isolation of few lines it sounds funny but when a programmer reads the whole file to assemble a picture what pystate is and what it is capable of, they read in zigzags only.

// Alias for backward compatibility with Python 3.8
#define _PyInterpreterState_Get PyInterpreterState_Get

Expand Down
4 changes: 0 additions & 4 deletions Include/internal/pycore_global_objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -606,10 +606,6 @@ struct _Py_global_objects {
}, \
}

static inline void
_Py_global_objects_reset(struct _Py_global_objects *objects)
{
}

#ifdef __cplusplus
}
Expand Down
30 changes: 29 additions & 1 deletion Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif

#include <stdbool.h>

#include "pycore_atomic.h" // _Py_atomic_address
#include "pycore_ast_state.h" // struct ast_state
#include "pycore_context.h" // struct _Py_context_state
Expand Down Expand Up @@ -70,13 +72,18 @@ struct atexit_state {

/* interpreter state */

// The PyInterpreterState typedef is in Include/pystate.h.
/* PyInterpreterState holds the global state for one of the runtime's
interpreters. Typically the initial (main) interpreter is the only one.

The PyInterpreterState typedef is in Include/pystate.h.
*/
struct _is {

struct _is *next;

struct pythreads {
uint64_t next_unique_id;
/* The linked list of threads, newest first. */
struct _ts *head;
/* Used in Modules/_threadmodule.c. */
long count;
Expand Down Expand Up @@ -104,6 +111,9 @@ struct _is {
int _initialized;
int finalizing;

/* Was this interpreter statically allocated? */
bool _static;

struct _ceval_state ceval;
struct _gc_runtime_state gc;

Expand Down Expand Up @@ -166,8 +176,26 @@ struct _is {

struct ast_state ast;
struct type_cache type_cache;

/* The following fields are here to avoid allocation during init.
The data is exposed through PyInterpreterState pointer fields.
These fields should not be accessed directly outside of init.

All other PyInterpreterState pointer fields are populated when
needed and default to NULL.

For now there are some exceptions to that rule, which require
allocation during init. These will be addressed on a case-by-case
basis. Also see _PyRuntimeState regarding the various mutex fields.
*/

/* the initial PyInterpreterState.threads.head */
struct _ts _initial_thread;
};


/* other API */

extern void _PyInterpreterState_ClearModules(PyInterpreterState *interp);
extern void _PyInterpreterState_Clear(PyThreadState *tstate);

Expand Down
48 changes: 38 additions & 10 deletions Include/internal/pycore_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ extern "C" {
#include "pycore_atomic.h" /* _Py_atomic_address */
#include "pycore_gil.h" // struct _gil_runtime_state
#include "pycore_global_objects.h" // struct _Py_global_objects
#include "pycore_interp.h" // struct _is
#include "pycore_unicodeobject.h" // struct _Py_unicode_runtime_ids


/* ceval state */

struct _ceval_runtime_state {
Expand Down Expand Up @@ -53,6 +55,9 @@ typedef struct _Py_AuditHookEntry {

/* Full Python runtime state */

/* _PyRuntimeState holds the global state for the CPython runtime.
That data is exposed in the internal API as a static variable (_PyRuntime).
*/
typedef struct pyruntimestate {
/* Has been initialized to a safe state.

Expand Down Expand Up @@ -81,7 +86,11 @@ typedef struct pyruntimestate {

struct pyinterpreters {
PyThread_type_lock mutex;
/* The linked list of interpreters, newest first. */
PyInterpreterState *head;
/* The runtime's initial interpreter, which has a special role
in the operation of the runtime. It is also often the only
interpreter. */
PyInterpreterState *main;
/* _next_interp_id is an auto-numbered sequence of small
integers. It gets initialized in _PyInterpreterState_Init(),
Expand Down Expand Up @@ -118,25 +127,44 @@ typedef struct pyruntimestate {

struct _Py_unicode_runtime_ids unicode_ids;

/* All the objects that are shared by the runtime's interpreters. */
struct _Py_global_objects global_objects;
// If anything gets added after global_objects then
// _PyRuntimeState_reset() needs to get updated to clear it.

/* The following fields are here to avoid allocation during init.
The data is exposed through _PyRuntimeState pointer fields.
These fields should not be accessed directly outside of init.

All other _PyRuntimeState pointer fields are populated when
needed and default to NULL.

For now there are some exceptions to that rule, which require
allocation during init. These will be addressed on a case-by-case
basis. Most notably, we don't pre-allocated the several mutex
(PyThread_type_lock) fields, because on Windows we only ever get
a pointer type.
*/

/* PyInterpreterState.interpreters.main */
PyInterpreterState _main_interpreter;
} _PyRuntimeState;

#define _PyThreadState_INIT \
{ \
._static = 1, \
}
#define _PyInterpreterState_INIT \
{ \
._static = 1, \
._initial_thread = _PyThreadState_INIT, \
}
#define _PyRuntimeState_INIT \
Copy link
Member

Choose a reason for hiding this comment

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

Might as well remove this as well. It is also only used once.

Copy link
Member Author

Choose a reason for hiding this comment

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

Should _Py_global_objects_INIT also move? It's pretty big and likely to get much bigger.

Copy link
Member Author

@ericsnowcurrently ericsnowcurrently Jan 10, 2022

Choose a reason for hiding this comment

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

Actually, _PyRuntimeState_INIT is used twice.

This comment was marked as off-topic.

{ \
.global_objects = _Py_global_objects_INIT, \
._main_interpreter = _PyInterpreterState_INIT, \
}
/* Note: _PyRuntimeState_INIT sets other fields to 0/NULL */

static inline void
_PyRuntimeState_reset(_PyRuntimeState *runtime)
{
/* Make it match _PyRuntimeState_INIT. */
memset(runtime, 0, (size_t)&runtime->global_objects - (size_t)runtime);
_Py_global_objects_reset(&runtime->global_objects);
}

/* other API */

PyAPI_DATA(_PyRuntimeState) _PyRuntime;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
The main interpreter in _PyRuntimeState.interpreters is now statically
allocated (as part of _PyRuntime). Likewise for the initial thread state of
each interpreter. This means less allocation during runtime init, as well
as better memory locality for these key state objects.
2 changes: 1 addition & 1 deletion Modules/signalmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ trip_signal(int sig_num)
_Py_atomic_store(&is_tripped, 1);

/* Signals are always handled by the main interpreter */
PyInterpreterState *interp = _PyRuntime.interpreters.main;
PyInterpreterState *interp = _PyInterpreterState_Main();

/* Notify ceval.c */
_PyEval_SignalReceived(interp);
Expand Down
2 changes: 1 addition & 1 deletion Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ Py_AddPendingCall(int (*func)(void *), void *arg)
}
else {
/* Last resort: use the main interpreter */
interp = _PyRuntime.interpreters.main;
interp = _PyInterpreterState_Main();
}
return _PyEval_AddPendingCall(interp, func, arg);
}
Expand Down
49 changes: 33 additions & 16 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ static PyThreadState *_PyGILState_GetThisThreadState(struct _gilstate_runtime_st
static void _PyThreadState_Delete(PyThreadState *tstate, int check_current);


/* We use "initial" if the runtime gets re-used
(e.g. Py_Finalize() followed by Py_Initialize(). */
static const _PyRuntimeState initial = _PyRuntimeState_INIT;

static int
alloc_for_runtime(PyThread_type_lock *plock1, PyThread_type_lock *plock2,
PyThread_type_lock *plock3)
Expand Down Expand Up @@ -91,9 +95,12 @@ init_runtime(_PyRuntimeState *runtime,
PyThread_type_lock xidregistry_mutex)
{
if (runtime->_initialized) {
_PyRuntimeState_reset(runtime);
assert(!runtime->initialized);
Py_FatalError("runtime already initialized");
}
assert(!runtime->preinitializing &&
!runtime->preinitialized &&
!runtime->core_initialized &&
!runtime->initialized);

runtime->open_code_hook = open_code_hook;
runtime->open_code_userdata = open_code_userdata;
Expand Down Expand Up @@ -144,6 +151,11 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
return _PyStatus_NO_MEMORY();
}

if (runtime->_initialized) {
// Py_Initialize() must be running again.
// Reset to _PyRuntimeState_INIT.
memcpy(runtime, &initial, sizeof(*runtime));
}
init_runtime(runtime, open_code_hook, open_code_userdata, audit_hook_head,
unicode_next_index, lock1, lock2, lock3);

Expand Down Expand Up @@ -250,13 +262,15 @@ alloc_interpreter(void)
static void
free_interpreter(PyInterpreterState *interp)
{
PyMem_RawFree(interp);
if (!interp->_static) {
PyMem_RawFree(interp);
}
}

/* Get the interpreter state to a minimal consistent state.
Further init happens in pylifecycle.c before it can be used.
All fields not initialized here are expected to be zeroed out,
e.g. by PyMem_RawCalloc() or memset().
e.g. by PyMem_RawCalloc() or memset(), or otherwise pre-initialized.
The runtime state is not manipulated. Instead it is assumed that
the interpreter is getting added to the runtime.
*/
Expand Down Expand Up @@ -338,23 +352,23 @@ PyInterpreterState_New(void)
assert(interpreters->main == NULL);
assert(id == 0);

interp = alloc_interpreter();
if (interp == NULL) {
goto error;
}
interp = &runtime->_main_interpreter;
assert(interp->id == 0);
assert(interp->next == NULL);

interpreters->main = interp;
}
else {
assert(id != 0);
assert(interpreters->main != NULL);
assert(id != 0);

interp = alloc_interpreter();
if (interp == NULL) {
goto error;
}
// Set to _PyInterpreterState_INIT.
memcpy(interp, &initial._main_interpreter,
sizeof(*interp));

if (id < 0) {
/* overflow or Py_Initialize() not called yet! */
Expand Down Expand Up @@ -735,13 +749,15 @@ alloc_threadstate(void)
static void
free_threadstate(PyThreadState *tstate)
{
PyMem_RawFree(tstate);
if (!tstate->_static) {
PyMem_RawFree(tstate);
}
}

/* Get the thread state to a minimal consistent state.
Further init happens in pylifecycle.c before it can be used.
All fields not initialized here are expected to be zeroed out,
e.g. by PyMem_RawCalloc() or memset().
e.g. by PyMem_RawCalloc() or memset(), or otherwise pre-initialized.
The interpreter state is not manipulated. Instead it is assumed that
the thread is getting added to the interpreter.
*/
Expand Down Expand Up @@ -808,10 +824,7 @@ new_threadstate(PyInterpreterState *interp)
// It's the interpreter's initial thread state.
assert(id == 1);

tstate = alloc_threadstate();
if (tstate == NULL) {
goto error;
}
tstate = &interp->_initial_thread;
}
else {
// Every valid interpreter must have at least one thread.
Expand All @@ -822,6 +835,10 @@ new_threadstate(PyInterpreterState *interp)
if (tstate == NULL) {
goto error;
}
// Set to _PyThreadState_INIT.
memcpy(tstate,
&initial._main_interpreter._initial_thread,
sizeof(*tstate));
}
interp->threads.head = tstate;

Expand Down Expand Up @@ -1159,7 +1176,7 @@ _PyThreadState_DeleteExcept(_PyRuntimeState *runtime, PyThreadState *tstate)
for (p = list; p; p = next) {
next = p->next;
PyThreadState_Clear(p);
PyMem_RawFree(p);
free_threadstate(p);
}
}

Expand Down