summaryrefslogtreecommitdiffstats
path: root/Python/pystate.c
diff options
context:
space:
mode:
authorEric Snow <ericsnowcurrently@gmail.com>2022-01-12 23:28:46 (GMT)
committerGitHub <noreply@github.com>2022-01-12 23:28:46 (GMT)
commited57b36c32e521162dbb97199e64a340d3bff827 (patch)
treefab825ae903723e4dc748e7b89cb78f3b226c816 /Python/pystate.c
parent0bbf30e2b910bc9c5899134ae9d73a8df968da35 (diff)
downloadcpython-ed57b36c32e521162dbb97199e64a340d3bff827.zip
cpython-ed57b36c32e521162dbb97199e64a340d3bff827.tar.gz
cpython-ed57b36c32e521162dbb97199e64a340d3bff827.tar.bz2
bpo-45953: Statically allocate the main interpreter (and initial thread state). (gh-29883)
Previously, the main interpreter was allocated on the heap during runtime initialization. Here we instead embed it into _PyRuntimeState, which means it is statically allocated as part of the _PyRuntime global. The same goes for the initial thread state (of each interpreter, including the main one). Consequently there are fewer allocations during runtime/interpreter init, fewer possible failures, and better memory locality. FYI, this also helps efforts to consolidate globals, which in turns helps work on subinterpreter isolation. https://bugs.python.org/issue45953
Diffstat (limited to 'Python/pystate.c')
-rw-r--r--Python/pystate.c49
1 files changed, 33 insertions, 16 deletions
diff --git a/Python/pystate.c b/Python/pystate.c
index 68fae8d..a18a159 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -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)
@@ -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,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! */
@@ -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.
*/
@@ -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.
@@ -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;
@@ -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);
}
}