Skip to content

Combining the argument-conversion loops in _PyEval_Vector #160679

Closed

Description

@ashm-dev

Feature or enhancement

In _PyEval_Vector, positional and keyword argument values are converted to _PyStackRef in separate loops. Both loops call PyStackRef_FromPyObjectNew.

The values are consecutive in args, and total_args already includes both groups. I replaced the two loops locally with:

for (size_t i = 0; i < total_args; i++) {
    arguments[i] = PyStackRef_FromPyObjectNew(args[i]);
}

This removes the duplicated conversion code and the second kwnames check. Conversion order and reference ownership stay the same. _PyEvalFramePushAndInit still receives the positional count, argcount, and kwnames separately.

The test suite passes with the change.

Is there a reason to keep the positional and keyword conversions separate?
Is there a reason to keep the positional and keyword conversions separate?

P.S. Even with -O3 -march=native, the compiler (gcc-16 and clang-23) leaves these as two separate loops in the optimized IR (LLVM IR | GCC GIMPLE), including the second kwnames check and keyword-count load. This suggests a small performance optimization opportunity, rather than just a source-code cleanup.

Has this already been discussed elsewhere?

No response given

Links to previous discussion of this feature:

https://discuss.python.org/t/combining-the-argument-conversion-loops-in-pyeval-vector/109635

Linked PRs

Activity

  1. sergey-miryanov commented on Oct 25, 2026

    @sergey-miryanov
    Contributor

    This suggests a small performance optimization opportunity

    Can we measure the speedup?

  2. ashm-dev commented on Oct 26, 2026

    @ashm-dev
    ContributorAuthor

    2. > This suggests a small performance optimization opportunity

    Can we measure the speedup?
    

    Here are the benchmark numbers using pyperf on a full PGO+LTO build.

    Results

    Benchmark main patch Difference
    vectorcall_kw 97.3 ns ± 0.8 ns 91.4 ns ± 1.2 ns ~1.06x faster (-6.1%)

    (Ran via pyperf compare_to main.json patch.json)

    Benchmark script & build flags
    # bench.py
    from functools import partial
    import pyperf
    
    def target(a, b, c=1, d=2):
        return a
    
    p = partial(target, 1, 2, c=3, d=4)
    
    runner = pyperf.Runner()
    runner.bench_func('vectorcall_kw', p)

    Build configuration:

    CC=clang CFLAGS='-O3 -march=tigerlake' \
    CXX=clang++ CXXFLAGS='-O3 -march=tigerlake' \
    LDFLAGS='-fuse-ld=lld' \
    ./configure --with-tail-call-interp --with-lto=full --enable-optimizations

    Eliminating the redundant keyword check and merging the conversion loops saves ~6 ns per call on mixed positional/keyword vectorcalls without introducing any behavioral changes.

  3. eendebakpt commented on Oct 26, 2026

    @eendebakpt
    Contributor

    @ashm-dev Can you open a PR?

  4. ashm-dev commented on Oct 26, 2026

    @ashm-dev
    ContributorAuthor

    @eendebakpt The changes are ready on my branch: https://github.com/ashm-dev/cpython/tree/gh-160679
    I'm currently hitting the 3 open PRs limit, so I can't submit it right now. Will open the PR as soon as a slot frees up

  5. added a commit that references this issue on Oct 29, 2026
  6. hauntsaninja commented on Oct 30, 2026

    @hauntsaninja
    Contributor

    Thanks for this microoptimisation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)performancePerformance or resource usagetype-featureA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions