diff options
author | Alexandre Vassalotti <alexandre@peadrop.com> | 2013-11-28 22:56:09 (GMT) |
---|---|---|
committer | Alexandre Vassalotti <alexandre@peadrop.com> | 2013-11-28 22:56:09 (GMT) |
commit | b13e6bcbd851c61af22c44dbe6f156653f54baae (patch) | |
tree | 62351f7cb4505e03b848b9d6abd4f0d2112b6407 /Modules/_pickle.c | |
parent | b6e66ebdf76c56b4a893c0687f91d1e6dd9cac2d (diff) | |
download | cpython-b13e6bcbd851c61af22c44dbe6f156653f54baae.zip cpython-b13e6bcbd851c61af22c44dbe6f156653f54baae.tar.gz cpython-b13e6bcbd851c61af22c44dbe6f156653f54baae.tar.bz2 |
Remove the tuple reuse optimization in _Pickle_FastCall.
I have noticed a race-condition occurring on one of the buildbots because of
this optimization. The function called may release the GIL which means
multiple threads may end up accessing the shared tuple. I could fix it up by
storing the tuple to the Pickler and Unipickler object again, but honestly it
really not worth the trouble.
I ran many benchmarks and the only time the optimization helps is when using a
fin-memory file, like io.BytesIO on which reads are super cheap, combined with
pickle protocol less than 4. Even in this contrived case, the speedup is a
about 5%. For everything else, this optimization does not provide any
noticable improvements.
Diffstat (limited to 'Modules/_pickle.c')
-rw-r--r-- | Modules/_pickle.c | 45 |
1 files changed, 15 insertions, 30 deletions
diff --git a/Modules/_pickle.c b/Modules/_pickle.c index a69a9aa..bb10118 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -169,8 +169,6 @@ typedef struct { /* As the name says, an empty tuple. */ PyObject *empty_tuple; - /* Single argument tuple used by _Pickle_FastCall */ - PyObject *arg_tuple; } PickleState; /* Forward declaration of the _pickle module definition. */ @@ -208,7 +206,6 @@ _Pickle_ClearState(PickleState *st) Py_CLEAR(st->import_mapping_3to2); Py_CLEAR(st->codecs_encode); Py_CLEAR(st->empty_tuple); - Py_CLEAR(st->arg_tuple); } /* Initialize the given pickle module state. */ @@ -328,8 +325,6 @@ _Pickle_InitState(PickleState *st) if (st->empty_tuple == NULL) goto error; - st->arg_tuple = NULL; - return 0; error: @@ -342,40 +337,31 @@ _Pickle_InitState(PickleState *st) /* Helper for calling a function with a single argument quickly. - This has the performance advantage of reusing the argument tuple. This - provides a nice performance boost with older pickle protocols where many - unbuffered reads occurs. - This function steals the reference of the given argument. */ static PyObject * _Pickle_FastCall(PyObject *func, PyObject *obj) { - PickleState *st = _Pickle_GetGlobalState(); - PyObject *arg_tuple; PyObject *result; + PyObject *arg_tuple = PyTuple_New(1); + + /* Note: this function used to reuse the argument tuple. This used to give + a slight performance boost with older pickle implementations where many + unbuffered reads occurred (thus needing many function calls). + + However, this optimization was removed because it was too complicated + to get right. It abused the C API for tuples to mutate them which led + to subtle reference counting and concurrency bugs. Furthermore, the + introduction of protocol 4 and the prefetching optimization via peek() + significantly reduced the number of function calls we do. Thus, the + benefits became marginal at best. */ - arg_tuple = st->arg_tuple; if (arg_tuple == NULL) { - arg_tuple = PyTuple_New(1); - if (arg_tuple == NULL) { - Py_DECREF(obj); - return NULL; - } + Py_DECREF(obj); + return NULL; } - assert(arg_tuple->ob_refcnt == 1); - assert(PyTuple_GET_ITEM(arg_tuple, 0) == NULL); - PyTuple_SET_ITEM(arg_tuple, 0, obj); result = PyObject_Call(func, arg_tuple, NULL); - - Py_CLEAR(PyTuple_GET_ITEM(arg_tuple, 0)); - if (arg_tuple->ob_refcnt > 1) { - /* The function called saved a reference to our argument tuple. - This means we cannot reuse it anymore. */ - Py_CLEAR(arg_tuple); - } - st->arg_tuple = arg_tuple; - + Py_CLEAR(arg_tuple); return result; } @@ -7427,7 +7413,6 @@ pickle_traverse(PyObject *m, visitproc visit, void *arg) Py_VISIT(st->import_mapping_3to2); Py_VISIT(st->codecs_encode); Py_VISIT(st->empty_tuple); - Py_VISIT(st->arg_tuple); return 0; } |