Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork32k
gh-119109: improvefunctools.partial
vectorcall with keywords#124584
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:main
Are you sure you want to change the base?
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
69ba0e9
9a21b55
f23021c
d840ad7
2dd7568
862097f
64c889b
a7142d5
ba36d01
acba269
898a104
10b9f3b
3647c25
f9e3fd4
42f3dc7
4f23211
acd9c56
cc557e9
e8fbaf8
1a8a56c
b3ff73d
00ebb4b
4575b6c
25a91aa
e326fcf
85d658f
cefa7d8
aa3a11f
8730ded
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
:func:`functools.partial` calls are now faster when keyword arguments are used. | ||
dg-pb marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -368,19 +368,6 @@ partial_descr_get(PyObject *self, PyObject *obj, PyObject *type) | ||
return PyMethod_New(self, obj); | ||
} | ||
static PyObject * | ||
partial_vectorcall(PyObject *self, PyObject *const *args, | ||
size_t nargsf, PyObject *kwnames) | ||
@@ -389,10 +376,7 @@ partial_vectorcall(PyObject *self, PyObject *const *args, | ||
PyThreadState *tstate = _PyThreadState_GET(); | ||
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); | ||
/* Placeholder check */ | ||
Py_ssize_t pto_phcount = pto->phcount; | ||
if (nargs < pto_phcount) { | ||
PyErr_Format(PyExc_TypeError, | ||
@@ -401,53 +385,143 @@ partial_vectorcall(PyObject *self, PyObject *const *args, | ||
return NULL; | ||
} | ||
PyObject **pto_args = _PyTuple_ITEMS(pto->args); | ||
Py_ssize_t pto_nargs = PyTuple_GET_SIZE(pto->args); | ||
Py_ssize_t pto_nkwds = PyDict_GET_SIZE(pto->kw); | ||
Py_ssize_t nkwds = kwnames == NULL ? 0 : PyTuple_GET_SIZE(kwnames); | ||
Py_ssize_t nargskw = nargs + nkwds; | ||
/* Special cases */ | ||
if (!pto_nkwds) { | ||
/* Fast path if we're called without arguments */ | ||
if (nargskw == 0) { | ||
return _PyObject_VectorcallTstate(tstate, pto->fn, pto_args, | ||
pto_nargs, NULL); | ||
} | ||
/* Use PY_VECTORCALL_ARGUMENTS_OFFSET to prepend a single | ||
* positional argument. */ | ||
if (pto_nargs == 1 && (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET)) { | ||
PyObject **newargs = (PyObject **)args - 1; | ||
PyObject *tmp = newargs[0]; | ||
newargs[0] = pto_args[0]; | ||
PyObject *ret = _PyObject_VectorcallTstate(tstate, pto->fn, newargs, | ||
nargs + 1, kwnames); | ||
newargs[0] = tmp; | ||
return ret; | ||
} | ||
} | ||
/* Total sizes */ | ||
Py_ssize_t tot_nargs = pto_nargs + nargs - pto_phcount; | ||
Py_ssize_t tot_nkwds = pto_nkwds + nkwds; | ||
Py_ssize_t tot_nargskw = tot_nargs + tot_nkwds; | ||
PyObject *pto_kw_merged = NULL; // pto_kw with duplicates merged (if any) | ||
PyObject *tot_kwnames; | ||
/* Allocate Stack | ||
* Note, _PY_FASTCALL_SMALL_STACK is optimal for positional only | ||
* This case might have keyword arguments | ||
* furthermore, it might use extra stack space for temporary key storage | ||
* thus, double small_stack size is used, which is 10 * 8 = 80 bytes */ | ||
PyObject *small_stack[_PY_FASTCALL_SMALL_STACK * 2]; | ||
PyObject **tmp_stack, **stack; | ||
Py_ssize_t init_stack_size = tot_nargskw; | ||
if (pto_nkwds) { | ||
// If pto_nkwds, allocate additional space for temporary new keys | ||
init_stack_size += nkwds; | ||
} | ||
if (init_stack_size <= (Py_ssize_t)Py_ARRAY_LENGTH(small_stack)) { | ||
stack = small_stack; | ||
} | ||
else { | ||
stack = PyMem_Malloc(init_stack_size * sizeof(PyObject *)); | ||
if (stack == NULL) { | ||
return PyErr_NoMemory(); | ||
} | ||
} | ||
/* Copy keywords to stack */ | ||
if (!pto_nkwds) { | ||
tot_kwnames = kwnames; | ||
if (nkwds) { | ||
/* if !pto_nkwds & nkwds, then simply append kw */ | ||
memcpy(stack + tot_nargs, args + nargs, nkwds * sizeof(PyObject*)); | ||
dg-pb marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
} | ||
} | ||
else { | ||
/* stack is now [<positionals>, <pto_kwds>, <kwds>, <kwds_keys>] | ||
* Will resize later to [<positionals>, <merged_kwds>] */ | ||
PyObject *key, *val; | ||
/* Merge kw to pto_kw or add to tail (if not duplicate) */ | ||
Py_ssize_t n_tail = 0; | ||
for (Py_ssize_t i = 0; i < nkwds; ++i) { | ||
key = PyTuple_GET_ITEM(kwnames, i); | ||
val = args[nargs + i]; | ||
if (PyDict_Contains(pto->kw, key)) { | ||
if (pto_kw_merged == NULL) { | ||
pto_kw_merged = PyDict_Copy(pto->kw); | ||
if (pto_kw_merged == NULL) { | ||
if (stack != small_stack) { | ||
PyMem_Free(stack); | ||
} | ||
return NULL; | ||
} | ||
} | ||
if (PyDict_SetItem(pto_kw_merged, key, val) < 0) { | ||
Py_DECREF(pto_kw_merged); | ||
if (stack != small_stack) { | ||
PyMem_Free(stack); | ||
} | ||
return NULL; | ||
} | ||
} | ||
else { | ||
/* Copy keyword tail to stack */ | ||
stack[tot_nargs + pto_nkwds + n_tail] = val; | ||
stack[tot_nargskw + n_tail] = key; | ||
n_tail++; | ||
} | ||
} | ||
Py_ssize_t n_merges = nkwds - n_tail; | ||
/* Create total kwnames */ | ||
tot_kwnames = PyTuple_New(tot_nkwds - n_merges); | ||
for (Py_ssize_t i = 0; i < n_tail; ++i) { | ||
key = Py_NewRef(stack[tot_nargskw + i]); | ||
PyTuple_SET_ITEM(tot_kwnames, pto_nkwds + i, key); | ||
} | ||
/* Copy pto_keywords with overlapping call keywords merged */ | ||
Py_ssize_t pos = 0, i = 0; | ||
while (PyDict_Next(n_merges ? pto_kw_merged : pto->kw, &pos, &key, &val)) { | ||
PyTuple_SET_ITEM(tot_kwnames, i, Py_NewRef(key)); | ||
stack[tot_nargs + i] = val; | ||
i++; | ||
} | ||
Py_XDECREF(pto_kw_merged); | ||
/* Resize Stack if the call has keywords */ | ||
if (nkwds && stack != small_stack) { | ||
tmp_stack = PyMem_Realloc(stack, (tot_nargskw - n_merges) * sizeof(PyObject *)); | ||
if (tmp_stack == NULL) { | ||
Py_DECREF(tot_kwnames); | ||
if (stack != small_stack) { | ||
PyMem_Free(stack); | ||
} | ||
return PyErr_NoMemory(); | ||
} | ||
stack = tmp_stack; | ||
} | ||
} | ||
/* Copy Positionals to stack */ | ||
if (pto_phcount) { | ||
Py_ssize_t j = 0; // New args index | ||
for (Py_ssize_t i = 0; i < pto_nargs; i++) { | ||
if (pto_args[i] == pto->placeholder){ | ||
stack[i] = args[j]; | ||
j += 1; | ||
} | ||
@@ -456,21 +530,24 @@ partial_vectorcall(PyObject *self, PyObject *const *args, | ||
} | ||
} | ||
assert(j == pto_phcount); | ||
/* Add remaining args from new_args */ | ||
if (nargs > pto_phcount) { | ||
memcpy(stack + pto_nargs, args + j, (nargs - j) * sizeof(PyObject*)); | ||
dg-pb marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
} | ||
} | ||
else { | ||
memcpy(stack, pto_args, pto_nargs * sizeof(PyObject*)); | ||
memcpy(stack + pto_nargs, args,nargs * sizeof(PyObject*)); | ||
dg-pb marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
} | ||
PyObject *ret = _PyObject_VectorcallTstate(tstate, pto->fn, stack, | ||
tot_nargs, tot_kwnames); | ||
if (stack != small_stack) { | ||
PyMem_Free(stack); | ||
} | ||
if (pto_nkwds) { | ||
Py_DECREF(tot_kwnames); | ||
} | ||
return ret; | ||
} | ||
Uh oh!
There was an error while loading.Please reload this page.