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

gh-86199: Avoid an unnecessary dict copy for f(**kwargs) calls by eendebakpt · Pull Request #156384 · python/cpython · GitHub

/ cpython Public

gh-86199: Avoid an unnecessary dict copy for f(**kwargs) calls - #156384

Open
eendebakpt wants to merge 1 commit into
python:mainfrom
eendebakpt:gh-86199-kwargs-nocopy-v2
Open

gh-86199: Avoid an unnecessary dict copy for f(**kwargs) calls#156384
eendebakpt wants to merge 1 commit into
python:mainfrom
eendebakpt:gh-86199-kwargs-nocopy-v2

Conversation

eendebakpt commented Aug 25, 2026
edited by bedevere-app Bot
Loading

Copy link
Copy Markdown
Contributor

This PR addresses #86199 (performance regression in f(**kw)) and #86795 (incorrect behavior of PyObject_Call leading to crashes). The work is based on gh-92192 by @MojoVampire, which went stale.

  • f(**d) with a small dict is ~1.4x faster in microbenchmarks.
  • PyObject_Call now honors its documented equivalence to callable(*args, **kwargs): a tp_call callee can no longer mutate the
    caller's kwargs dict.
  • Unpacking a shared kwargs dict is now safe against concurrent resizing on the free-threaded build.

It took some iterations before ending up at the current PR. The main optimization is due to the compiler pushing the ** operand as-is instead of BUILD_MAP/DICT_MERGE. CALL_FUNCTION_EX converts a non-exact mapping to an exact dict itself, reusing the DICT_MERGE machinery so error messages are unchanged. PyObject_Call copies the dict only on the tp_call path, where the callee would otherwise receive the caller's dict directly. For the vectorcall protocoll the _PyStack_UnpackDict takes a critical section while copying the dict's items out (retrying on a concurrent resize).

Since Python 3.9 a call with a lone ** unpacking compiled to
BUILD_MAP 0 + DICT_MERGE 1 + CALL_FUNCTION_EX, copying the kwargs dict on
every call.  For vectorcall callees (all Python functions and most
builtins) that copy is wasted work: the dict is immediately unpacked onto
a flat argument vector.

The compiler now pushes the ** operand as-is.  CALL_FUNCTION_EX converts a
non-exact mapping to a dict itself (reusing the DICT_MERGE machinery, so
error messages are unchanged), and PyObject_Call copies the dict only on
the tp_call path, where the callee would otherwise receive the caller's
dict directly -- so a callee still can never mutate the caller's kwargs,
and the documented PyObject_Call/callable(*args, **kwargs) equivalence now
holds for the C API too (pythongh-86795).

_PyStack_UnpackDict takes a critical section while copying the dict's
items out, retrying if the dict was resized in between, which makes
unpacking a shared dict safe on free-threaded builds.

f(**d) with a small dict is ~1.4x faster; calls without ** unpacking are
unaffected.

Co-authored-by: Josh Rosenberg <1178095+MojoVampire@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant


Back | FazBrowse Home | New Git URL