FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-148072: Cache pickle.dumps/loads per interpreter in XIData by jrfk · Pull Request #148125 · python/cpython · GitHub

/ cpython Public
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .c  (1) .h  (1) .rst  (1) All 3 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
6 changes: 6 additions & 0 deletions Include/internal/pycore_crossinterp.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,12 @@ typedef struct {
// heap types
PyObject *PyExc_NotShareableError;
} exceptions;

// Cached references to pickle.dumps/loads (per-interpreter).
struct {
PyObject *dumps;
PyObject *loads;
} pickle;
} _PyXI_state_t;

#define _PyXI_GET_GLOBAL_STATE(interp) (&(interp)->runtime->xi)
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Cache ``pickle.dumps`` and ``pickle.loads`` per interpreter in the XIData
framework, avoiding repeated module lookups on every cross-interpreter data
transfer. This speeds up :class:`~concurrent.futures.InterpreterPoolExecutor`
for mutable types (``list``, ``dict``) by 1.7x--3.3x.
62 changes: 56 additions & 6 deletions Python/crossinterp.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -568,20 +568,61 @@ _PyObject_GetXIData(PyThreadState *tstate,

/* pickle C-API */

/* Per-interpreter cache for pickle.dumps and pickle.loads.
*
* Each interpreter has its own cache in _PyXI_state_t.pickle, preserving
* interpreter isolation. The cache is populated lazily on first use and
* cleared during interpreter finalization in _Py_xi_state_fini().
*
* Note: the cached references are captured at first use and not invalidated
* on module reload. This matches the caching pattern used elsewhere in
* CPython (e.g. arraymodule.c, _decimal.c). */

static PyObject *
_get_pickle_dumps(PyThreadState *tstate)
{
_PyXI_state_t *state = _PyXI_GET_STATE(tstate->interp);
PyObject *dumps = state->pickle.dumps;
if (dumps != NULL) {
return dumps;
}
dumps = PyImport_ImportModuleAttrString("pickle", "dumps");
if (dumps == NULL) {
return NULL;
}
state->pickle.dumps = dumps; // owns the reference
return dumps;
}

static PyObject *
_get_pickle_loads(PyThreadState *tstate)
{
_PyXI_state_t *state = _PyXI_GET_STATE(tstate->interp);
PyObject *loads = state->pickle.loads;
if (loads != NULL) {
return loads;
}
loads = PyImport_ImportModuleAttrString("pickle", "loads");
if (loads == NULL) {
return NULL;
}
state->pickle.loads = loads; // owns the reference
return loads;
}

struct _pickle_context {
PyThreadState *tstate;
};

static PyObject *
_PyPickle_Dumps(struct _pickle_context *ctx, PyObject *obj)
{
PyObject *dumps = PyImport_ImportModuleAttrString("pickle", "dumps");
PyObject *dumps = _get_pickle_dumps(ctx->tstate);
if (dumps == NULL) {
return NULL;
}
PyObject *bytes = PyObject_CallOneArg(dumps, obj);
Py_DECREF(dumps);
return bytes;
// dumps is a borrowed reference from the cache.
return PyObject_CallOneArg(dumps, obj);
}


Expand Down Expand Up @@ -636,7 +677,8 @@ _PyPickle_Loads(struct _unpickle_context *ctx, PyObject *pickled)
PyThreadState *tstate = ctx->tstate;

PyObject *exc = NULL;
PyObject *loads = PyImport_ImportModuleAttrString("pickle", "loads");
// loads is a borrowed reference from the per-interpreter cache.
PyObject *loads = _get_pickle_loads(tstate);
if (loads == NULL) {
return NULL;
}
Expand Down Expand Up @@ -682,7 +724,6 @@ _PyPickle_Loads(struct _unpickle_context *ctx, PyObject *pickled)
// It might make sense to chain it (__context__).
_PyErr_SetRaisedException(tstate, exc);
}
Py_DECREF(loads);
return obj;
}

Expand Down Expand Up @@ -3094,6 +3135,10 @@ _Py_xi_state_init(_PyXI_state_t *state, PyInterpreterState *interp)
assert(state != NULL);
assert(interp == NULL || state == _PyXI_GET_STATE(interp));

// Initialize pickle function cache (before any fallible ops).
state->pickle.dumps = NULL;
state->pickle.loads = NULL;

xid_lookup_init(&state->data_lookup);

// Initialize exceptions.
Expand All @@ -3116,6 +3161,11 @@ _Py_xi_state_fini(_PyXI_state_t *state, PyInterpreterState *interp)
assert(state != NULL);
assert(interp == NULL || state == _PyXI_GET_STATE(interp));

// Clear pickle function cache first: the cached functions may hold
// references to modules cleaned up by later finalization steps.
Py_CLEAR(state->pickle.dumps);
Py_CLEAR(state->pickle.loads);

fini_heap_exctypes(&state->exceptions);
if (interp != NULL) {
fini_static_exctypes(&state->exceptions, interp);
Expand Down
Loading

Back | FazBrowse Home | New Git URL