-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
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
Changes from 60 commits
5e6eda9
de12b98
035eff3
1569a53
518e3f3
9995a84
f22d0da
cf39d57
b840213
3bf69b0
4de466e
9bb32a6
385f298
354997f
9bbf8a0
8d1cf35
188aed8
e0da483
46c7767
7ca8d1c
ed6c273
29e1764
1a83910
8631805
a0569bb
4c3c0f9
47bfa7e
dd69ffa
ba75803
eef3194
8703297
d1eddad
8a8150c
7a37237
b2073ac
21a522d
7b376d4
562ee42
ed8f5cd
6da97b7
cf24942
fac6af6
4aa2073
f135168
2b4ba04
24d1de2
84ae7db
e9de30c
51a32ce
7104518
f7a8389
18080a1
0457119
923ca7a
99c2ae1
3bc73ee
c1808ae
7b98e59
0383eae
c959db0
5b34b42
837bfe6
ebefa35
eb7a080
1ef4a39
771e574
b5c43f1
bfc3906
9b25c8b
e776c31
c6bda1f
8ab6261
ff8de28
b28afb3
0d30300
e193794
e9f1262
a6a6471
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
|
||
|
@@ -82,6 +85,7 @@ struct _ts { | |
In order to be effective, this must be set to 0 during or right | ||
after allocation. */ | ||
int _initialized; | ||
bool _static; | ||
|
||
int recursion_remaining; | ||
int recursion_limit; | ||
|
@@ -176,8 +180,24 @@ struct _ts { | |
PyObject **datastack_limit; | ||
/* XXX signal handlers should also be here */ | ||
|
||
struct { | ||
// See _PyRuntimeState._preallocated for an explanation. | ||
// XXX Move exc_state and root_cframe down here. | ||
// XXX Allocate the initial datastack_chunk here.. | ||
bool _dummy; // We'll remove this as soon as we pre-allocate something. | ||
} _preallocated; | ||
}; | ||
|
||
#ifdef Py_BUILD_CORE | ||
#define _PyThreadState_INIT \ | ||
{ \ | ||
._static = 1, \ | ||
} | ||
#endif | ||
|
||
|
||
/* other API */ | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 } There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the tip. What's the advantage? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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_bytesobject.h" // struct _Py_bytes_state | ||
|
@@ -104,6 +106,7 @@ struct _is { | |
after allocation. */ | ||
int _initialized; | ||
int finalizing; | ||
bool _static; | ||
|
||
struct _ceval_state ceval; | ||
struct _gc_runtime_state gc; | ||
|
@@ -168,8 +171,25 @@ struct _is { | |
|
||
struct ast_state ast; | ||
struct type_cache type_cache; | ||
|
||
struct { | ||
// See _PyRuntimeState._preallocated for an explanation. | ||
PyThreadState tstate; | ||
// XXX Pre-allocate as many objects from above as possible here. | ||
} _preallocated; | ||
ericsnowcurrently marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
#define _PyInterpreterState_INIT \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is only used once, please remove it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI, I brought this back to avoid so much nesting and clutter in |
||
{ \ | ||
._static = 1, \ | ||
._preallocated = { \ | ||
.tstate = _PyThreadState_INIT, \ | ||
}, \ | ||
} | ||
|
||
|
||
/* other API */ | ||
|
||
extern void _PyInterpreterState_ClearModules(PyInterpreterState *interp); | ||
extern void _PyInterpreterState_Clear(PyThreadState *tstate); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
|
@@ -118,25 +120,32 @@ typedef struct pyruntimestate { | |
|
||
struct _Py_unicode_runtime_ids unicode_ids; | ||
|
||
struct _Py_global_objects global_objects; | ||
// If anything gets added after global_objects then | ||
// _PyRuntimeState_reset() needs to get updated to clear it. | ||
struct { | ||
// The fields here are values that would otherwise have been | ||
// malloc'ed during runtime init in pystate.c and pylifecycle.c. | ||
// This allows us to avoid allocation costs during startup and | ||
// helps simplify the startup code. | ||
struct _Py_global_objects global_objects; | ||
struct _is interpreters_main; | ||
ericsnowcurrently marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// The only other values to possibly include here are | ||
// mutexes (PyThread_type_lock). Currently we don't pre-allocate them | ||
// because on Windows we only get a pointer type. | ||
// All other pointers in PyThreadState are either | ||
// populated lazily or change but default to NULL. | ||
} _preallocated; | ||
ericsnowcurrently marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} _PyRuntimeState; | ||
|
||
#define _PyRuntimeState_INIT \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually,
This comment was marked as off-topic.
Sorry, something went wrong. |
||
{ \ | ||
.global_objects = _Py_global_objects_INIT, \ | ||
._preallocated = { \ | ||
.global_objects = _Py_global_objects_INIT, \ | ||
.interpreters_main = _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; | ||
|
||
|
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 _PyRuntimeState initial = _PyRuntimeState_INIT; | ||
|
||
static int | ||
alloc_for_runtime(PyThread_type_lock *plock1, PyThread_type_lock *plock2, | ||
PyThread_type_lock *plock3) | ||
|
@@ -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; | ||
|
@@ -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); | ||
|
||
|
@@ -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. | ||
*/ | ||
|
@@ -338,10 +352,7 @@ PyInterpreterState_New(void) | |
assert(interpreters->main == NULL); | ||
assert(id == 0); | ||
|
||
interp = alloc_interpreter(); | ||
if (interp == NULL) { | ||
goto error; | ||
} | ||
interp = &runtime->_preallocated.interpreters_main; | ||
assert(interp->id == 0); | ||
assert(interp->next == NULL); | ||
|
||
|
@@ -355,6 +366,9 @@ PyInterpreterState_New(void) | |
if (interp == NULL) { | ||
goto error; | ||
} | ||
// Set to _PyInterpreterState_INIT. | ||
memcpy(interp, &initial._preallocated.interpreters_main, | ||
sizeof(*interp)); | ||
|
||
if (id < 0) { | ||
/* overflow or Py_Initialize() not called yet! */ | ||
|
@@ -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. | ||
*/ | ||
|
@@ -816,10 +832,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->_preallocated.tstate; F438 span> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like unnecessary complexity. The main interpreter has a pre-allocated tstate, so won't hit this code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All interpreters have an initial thread (which will likely be its only thread).
The first thread state is statically allocated for all interpreters, not just the main interpreter. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is that possible? You don't know how many interpreters there will be. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for the confusion. I meant that the initial thread state for each |
||
} | ||
else { | ||
// Every valid interpreter must have at least one thread. | ||
|
@@ -830,6 +843,10 @@ new_threadstate(PyInterpreterState *interp) | |
if (tstate == NULL) { | ||
goto error; | ||
} | ||
// Set to _PyThreadState_INIT. | ||
memcpy(tstate, | ||
&initial._preallocated.interpreters_main._preallocated.tstate, | ||
sizeof(*tstate)); | ||
} | ||
interp->threads.head = tstate; | ||
|
||
|
@@ -1170,7 +1187,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); | ||
} | ||
} | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.