Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Open
dg-pb wants to merge29 commits intopython:main
base:main
Choose a base branch
Loading
fromdg-pb:gh-119109-partial_vectorcall_kw
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
29 commits
Select commitHold shift + click to select a range
69ba0e9
initial implementation
dg-pbSep 26, 2024
9a21b55
V2
dg-pbSep 26, 2024
f23021c
small fixes
dg-pbSep 26, 2024
d840ad7
V3
dg-pbSep 26, 2024
2dd7568
V4
dg-pbSep 27, 2024
862097f
fix compiler warnings
dg-pbSep 27, 2024
64c889b
V5 stable
dg-pbSep 29, 2024
a7142d5
add commented fix if merging after gh-124652
dg-pbSep 30, 2024
ba36d01
error check
dg-pbOct 4, 2024
acba269
fix error check
dg-pbOct 4, 2024
898a104
minor macro edit
dg-pbOct 5, 2024
10b9f3b
merge to main
dg-pbOct 17, 2024
3647c25
📜🤖 Added by blurb_it.
blurb-it[bot]Oct 17, 2024
f9e3fd4
small edits
dg-pbDec 18, 2024
42f3dc7
idiomatic C
dg-pbJan 5, 2025
4f23211
Merge remote-tracking branch 'upstream/main' into gh-119109-partial_v…
dg-pbJan 5, 2025
acd9c56
small edits and fixes
dg-pbJan 5, 2025
cc557e9
macros removed, post-resizing instead
dg-pbJan 5, 2025
e8fbaf8
regain previous performance
dg-pbJan 6, 2025
1a8a56c
small edit
dg-pbJan 6, 2025
b3ff73d
labels removed
dg-pbJan 6, 2025
00ebb4b
comment edits
dg-pbJan 6, 2025
4575b6c
minor fixes and improvements
dg-pbJan 6, 2025
25a91aa
small stack size doubled + small edits
dg-pbJan 8, 2025
e326fcf
removed commented fix when trailing placeholders allowed
dg-pbJan 9, 2025
85d658f
moved declarations to more sensible place
dg-pbJan 10, 2025
cefa7d8
reorder declarations
dg-pbJan 10, 2025
aa3a11f
Merge remote-tracking branch 'upstream/main' into gh-119109-partial_v…
dg-pbMay 8, 2025
8730ded
Merge branch 'main' into gh-119109-partial_vectorcall_kw
kumaraditya303May 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
:func:`functools.partial` calls are now faster when keyword arguments are used.
185 changes: 131 additions & 54 deletionsModules/_functoolsmodule.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -368,19 +368,6 @@ partial_descr_get(PyObject *self, PyObject *obj, PyObject *type)
return PyMethod_New(self, obj);
}

/* Merging keyword arguments using the vectorcall convention is messy, so
* if we would need to do that, we stop using vectorcall and fall back
* to using partial_call() instead. */
Py_NO_INLINE static PyObject *
partial_vectorcall_fallback(PyThreadState *tstate, partialobject *pto,
PyObject *const *args, size_t nargsf,
PyObject *kwnames)
{
pto->vectorcall = NULL;
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
return _PyObject_MakeTpCall(tstate, (PyObject *)pto, args, nargs, kwnames);
}

static PyObject *
partial_vectorcall(PyObject *self, PyObject *const *args,
size_t nargsf, PyObject *kwnames)
Expand All@@ -389,10 +376,7 @@ partial_vectorcall(PyObject *self, PyObject *const *args,
PyThreadState *tstate = _PyThreadState_GET();
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);

/* pto->kw is mutable, so need to check every time */
if (PyDict_GET_SIZE(pto->kw)) {
return partial_vectorcall_fallback(tstate, pto, args, nargsf, kwnames);
}
/* Placeholder check */
Py_ssize_t pto_phcount = pto->phcount;
if (nargs < pto_phcount) {
PyErr_Format(PyExc_TypeError,
Expand All@@ -401,53 +385,143 @@ partial_vectorcall(PyObject *self, PyObject *const *args,
return NULL;
}

Py_ssize_t nargskw = nargs;
if (kwnames != NULL) {
nargskw += PyTuple_GET_SIZE(kwnames);
}

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);
}

/* 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;
}
}

/* Fast path using 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 *small_stack[_PY_FASTCALL_SMALL_STACK];
PyObject **stack;
PyObject *pto_kw_merged = NULL; // pto_kw with duplicates merged (if any)
PyObject *tot_kwnames;

Py_ssize_t tot_nargskw = pto_nargs + nargskw - pto_phcount;
if (tot_nargskw <= (Py_ssize_t)Py_ARRAY_LENGTH(small_stack)) {
/* 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(tot_nargskw * sizeof(PyObject *));
stack = PyMem_Malloc(init_stack_size * sizeof(PyObject *));
if (stack == NULL) {
PyErr_NoMemory();
return 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*));
}
}
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;
}
}

Py_ssize_t tot_nargs;
/* Copy Positionals to stack */
if (pto_phcount) {
tot_nargs = pto_nargs + nargs - 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){
if (pto_args[i] == pto->placeholder){
stack[i] = args[j];
j += 1;
}
Expand All@@ -456,21 +530,24 @@ partial_vectorcall(PyObject *self, PyObject *const *args,
}
}
assert(j == pto_phcount);
if (nargskw > pto_phcount) {
memcpy(stack + pto_nargs, args + j, (nargskw - j) * sizeof(PyObject*));
/* Add remaining args from new_args */
if (nargs > pto_phcount) {
memcpy(stack + pto_nargs, args + j, (nargs - j) * sizeof(PyObject*));
}
}
else {
tot_nargs = pto_nargs + nargs;
/* Copy to new stack, using borrowed references */
memcpy(stack, pto_args, pto_nargs * sizeof(PyObject*));
memcpy(stack + pto_nargs, args,nargskw * sizeof(PyObject*));
memcpy(stack + pto_nargs, args,nargs * sizeof(PyObject*));
}
PyObject *ret = _PyObject_VectorcallTstate(tstate, pto->fn,
stack, tot_nargs, kwnames);

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;
}

Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp