Skip to content

Combining the argument-conversion loops in _PyEval_Vector #160679

Open

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

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

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