You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Generate vectorcall code to parse arguments using Argument Clinic #87613
Dong-hee, did you ever get to this? :) If so, I'd be interested in hearing what you found out.
Sorry, I didn't make progress on this issue. One thing I considered at that moment was supporting positional arguments only cases first. Implementing keyword arguments will be quite complex and not many use cases exist we should cover. so it is not urgent.
If you want to take a look at this issue, it will be fine.
Sorry, I didn't make progress on this issue. One thing I considered at that moment was supporting positional arguments only cases first. Implementing keyword arguments will be quite complex and not many use cases exist we should cover. so it is not urgent.
If you want to take a look at this issue, it will be fine.
No need to be sorry :) I don't have the bandwidth to look at it right now, but I hope to be able to pick it up this fall. Let's keep the issue open. If you find time for this, that is great, but there is no urgency :)
Taking a look at this scoped down to __new__ and __init__ which should be able to do float, bytes, str, and bytearray to simplify and optimize some cases. My plan is to try making a @vectorcall decorator in clinic which causes two parse arg codegens to output: one for the existing, a second which adds an _vectorcall variant. What I'm experimenting with is the following input:
My plan is to try making a @vectorcall decorator in clinic which causes two parse arg codegens to output: one for the existing, a second which adds an _vectorcall variant.
That sounds great, I am looking forward to use it to speed up asyncio task creation which would benefit from this.
Have a working implementation with performance matching or beating the currently handwritten implementations. Benchmark and full numbers below. Overall 1.03x faster.
First PR: GH-145381. The changes I have split across a couple commits in my draft ac_vectorcall_v1 branch. Did a PR for just adding the support to AC; then not sure what would be best for the other changes (leaning: PR per type for easy reverts if needed?).
Adding @vectorcall decorator and codegen with test
Commit per type, replacing hand-coded vectorcall implementations with AC generated ones
Adding @vectorcall to additional types.
Microbenchmark for different constructions. Not planning to try and get into CPython; think it could be useful to track perf over time but not sure a good place for it to live.
Performance numbers
Benchmark code: cmaloney@ddcd3b6
Configure options: ./configure --enable-optimizations --with-lto --with-static-libpython
Host: 64 bit Arch Linux
Benchmark
bench_baseline2
bench_vectorcall2
list(tuple)
303 ns
291 ns: 1.04x faster
list_subclass
385 ns
395 ns: 1.02x slower
bytes()
111 ns
89.5 ns: 1.24x faster
bytes(int)
281 ns
224 ns: 1.26x faster
bytearray()
210 ns
193 ns: 1.09x faster
bytearray(bytes)
445 ns
394 ns: 1.13x faster
bytearray(int)
190 ns
178 ns: 1.06x faster
int()
73.5 ns
71.4 ns: 1.03x faster
int(str)
175 ns
182 ns: 1.04x slower
enumerate(list)
318 ns
329 ns: 1.04x slower
Geometric mean
(ref)
1.03x faster
Benchmark hidden because not significant (13): list(), list(range), float(), float(int), float(str), str(), str(int), str(bytes,enc), tuple(), tuple(list), int(str,base), reversed(list), enumerate(list,start)
Reacted by Sergey Miryanov, Donghee Na, Chris Eibl and Erlend E. Aasland
Cleaned this up to no longer require static C type declarations and updated existing clinic tests. Adjusts the codegen type checks slightly but I think overall nicer: #157968
Continuing to iterate on this. For str, list, and int the generated vectorcall is slower than the hand-written vectorcall because the hand written inlines special cases which Argument Clinic has no way, currently, of knowing about. The slowdown is ~20% on microbenchmarks. I built a prototype I call "pyspec" which would make these inlnining critical special cases visible to AC as well as available to pieces like the JIT. The core is to use plain python to write the if tree to get to C functions then lower that to C. That makes it so AC no longer reuqires the custom language/parser. Nothing revolutionary in the python ecosystems but I think has some nice side-effects.
This currently only supports static types. GH-157968 makes it support non-static types. That is necessary to support Decimal. That makes both heap and static types supported and tested. It would be nice to get reviewed as it changes the asserts in generated code a little.
As a demonstration of speedup and applying to new types I put together GH-158891 for bytes and bytearray. That turns on one of the core things I hope to enable with this work: minimal copy bytes(bytearray(bytes(bytearray(b'123'))). Before Vectorcall there were references in arguments so every constructor had to copy. Now there is a uniquely referenced temporaty where we can "pass along" the storage avoiding copies.
Continuing to iterate on this. For str, list, and int the generated vectorcall is slower than the hand-written vectorcall because the hand written inlines special cases which Argument Clinic has no way, currently, of knowing about.
What is missing from AC generated code to get the same speed?
In the first iteration I called this zero_arg, one_arg etc. To solve that I'm hoping to make the information available both to Argument Clinic and projects like the JIT. That was too specialized of infra in a lot of ways.
Currently functions like bytes_new_impl are fully opaque. I'm hoping to make them be less opaque by giving the base decision tree in some Python syntax. Need some help from core devs what is preferred (an if tree like existing code, match statements, overloads, ...). That keeps to very basic operations and can lower directly from AST -> C as well as building an "overload" table that the JIT can potentially consume.
The hope is we can have one core definition which we can efficiently codegen to the set of functions we need + metadata that says "single unique bytearray arg -> use this C function directly". That should reduce the number of instructions needed while keeping things maintainable. Argument for pushing towards Python syntax is to make it look just like everything else the JIT must optimize / less special cases and less custom parsers.
Activity
erlend-aasland commentedon Aug 17, 2023
Dong-hee, did you ever get to this? :) If so, I'd be interested in hearing what you found out.
corona10 commentedon Aug 17, 2023
Sorry, I didn't make progress on this issue. One thing I considered at that moment was supporting positional arguments only cases first. Implementing keyword arguments will be quite complex and not many use cases exist we should cover. so it is not urgent.
If you want to take a look at this issue, it will be fine.
corona10 commentedon Aug 17, 2023
enumrate is good example when you want to handle keyword arguments. as you can read it's too complex.
cpython/Objects/enumobject.c
Lines 101 to 150 in 1344cfa
erlend-aasland commentedon Aug 17, 2023
No need to be sorry :) I don't have the bandwidth to look at it right now, but I hope to be able to pick it up this fall. Let's keep the issue open. If you find time for this, that is great, but there is no urgency :)
cmaloney commentedon Nov 9, 2025
Taking a look at this scoped down to
__new__and__init__which should be able to dofloat,bytes,str, andbytearrayto simplify and optimize some cases. My plan is to try making a@vectorcalldecorator in clinic which causes two parse arg codegens to output: one for the existing, a second which adds an_vectorcallvariant. What I'm experimenting with is the following input:kumaraditya303 commentedon Dec 15, 2025
That sounds great, I am looking forward to use it to speed up asyncio task creation which would benefit from this.
pythongh-87613: Add vectorcall constructor benchmarks
pythongh-87613: Add @vectorcall decorator support to Argument Clinic
pythongh-87613: Migrate list.__init__ to clinic-generated vectorcall
pythongh-87613: Add vectorcall to bytearray.__init__ via Argument Clinic
pythongh-87613: Migrate float.__new__ to clinic-generated vectorcall
pythongh-87613: Add vectorcall to bytes.__new__ via Argument Clinic
pythongh-87613: Migrate str.__new__ to clinic-generated vectorcall
pythongh-87613: Add vectorcall constructor benchmarks
pythongh-87613: Add @vectorcall decorator support to Argument Clinic
pythongh-87613: Migrate list.__init__ to clinic-generated vectorcall
pythongh-87613: Add vectorcall to bytearray.__init__ via Argument Clinic
pythongh-87613: Migrate float.__new__ to clinic-generated vectorcall
pythongh-87613: Add vectorcall to bytes.__new__ via Argument Clinic
pythongh-87613: Migrate str.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate tuple.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate int.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate enumerate and reversed to clinic-generated ve…
pythongh-87613: Add @vectorcall decorator support to Argument Clinic
pythongh-87613: Migrate list.__init__ to clinic-generated vectorcall
pythongh-87613: Add vectorcall to bytearray.__init__ via Argument Clinic
pythongh-87613: Migrate float.__new__ to clinic-generated vectorcall
pythongh-87613: Add vectorcall to bytes.__new__ via Argument Clinic
pythongh-87613: Migrate str.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate tuple.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate int.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate enumerate and reversed to clinic-generated ve…
pythongh-87613: Add @vectorcall decorator support to Argument Clinic
pythongh-87613: Migrate list.__init__ to clinic-generated vectorcall
pythongh-87613: Add vectorcall to bytearray.__init__ via Argument Clinic
pythongh-87613: Migrate float.__new__ to clinic-generated vectorcall
pythongh-87613: Add vectorcall to bytes.__new__ via Argument Clinic
pythongh-87613: Migrate str.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate tuple.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate int.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate enumerate and reversed to clinic-generated ve…
pythongh-87613: Add @vectorcall decorator support to Argument Clinic
pythongh-87613: Migrate list.__init__ to clinic-generated vectorcall
pythongh-87613: Add vectorcall to bytearray.__init__ via Argument Clinic
pythongh-87613: Migrate float.__new__ to clinic-generated vectorcall
pythongh-87613: Add vectorcall to bytes.__new__ via Argument Clinic
pythongh-87613: Migrate str.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate tuple.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate int.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate enumerate and reversed to clinic-generated ve…
pythongh-87613: Add vectorcall constructor benchmarks
pythongh-87613: Add @vectorcall decorator support to Argument Clinic
pythongh-87613: Migrate list.__init__ to clinic-generated vectorcall
pythongh-87613: Add vectorcall to bytearray.__init__ via Argument Clinic
pythongh-87613: Migrate float.__new__ to clinic-generated vectorcall
pythongh-87613: Add vectorcall to bytes.__new__ via Argument Clinic
pythongh-87613: Migrate str.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate tuple.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate int.__new__ to clinic-generated vectorcall
pythongh-87613: Migrate enumerate and reversed to clinic-generated ve…
pythongh-87613: Migrate enumerate and reversed to clinic-generated ve…
pythongh-87613: Add @vectorcall decorator support to Argument Clinic
pythongh-87613: Argument Cliic @vectorcall decorator
pythongh-87613: Argument Cliic @vectorcall decorator
cmaloney commentedon Mar 1, 2026
Have a working implementation with performance matching or beating the currently handwritten implementations. Benchmark and full numbers below. Overall 1.03x faster.
First PR: GH-145381. The changes I have split across a couple commits in my draft ac_vectorcall_v1 branch. Did a PR for just adding the support to AC; then not sure what would be best for the other changes (leaning: PR per type for easy reverts if needed?).
@vectorcalldecorator and codegen with test@vectorcallto additional types.Performance numbers
Benchmark code: cmaloney@ddcd3b6
Configure options:
./configure --enable-optimizations --with-lto --with-static-libpythonHost: 64 bit Arch Linux
Benchmark hidden because not significant (13): list(), list(range), float(), float(int), float(str), str(), str(int), str(bytes,enc), tuple(), tuple(list), int(str,base), reversed(list), enumerate(list,start)
gh-87613: Argument Clinic vectorcall decorator (GH-145381)
encukou commentedon Sep 10, 2026
Thank you for the improvement, @cmaloney!
The docs for AC live in the devguide; would you mind making a PR there as well?
pythongh-87613: Argument Clinic vectorcall decorator (pythonGH-145381)
pythongh-87613: Argument Clinic vectorcall decorator (pythonGH-145381)
cmaloney commentedon Sep 22, 2026
Working on the devguide updates + moving more types w/ pyperformance numbers. A number of benchmarks seemed to get slower on the day this was merged / September 10th (ex. https://speed.python.org/timeline/#/?exe=12&ben=async_tree_eager&env=1&revs=50&equid=off&quarts=on&extr=on). Investigating the root cause.
Cleaned this up to no longer require static C type declarations and updated existing clinic tests. Adjusts the codegen type checks slightly but I think overall nicer: #157968
StanFromIreland commentedon Sep 25, 2026
The
async_tree_basebenchmark onmemory.python.orgalso shows a significant regression.cmaloney commentedon Sep 25, 2026
Bisected and memory + runtime increase seems to be from gh-157213, not this change. Left more details there.
pythongh-87613: Use the AC vectorcall for bytes and bytearray
pythongh-87613: Add bytes/bytearray constructor benchmarks
Merge remote-tracking branch 'upstream/main' into pythongh-87613-float2
gh-87613: Use AC vectorcall for float (GH-158201)
cmaloney commentedon Oct 13, 2026
Continuing to iterate on this. For
str,list, andintthe generated vectorcall is slower than the hand-written vectorcall because the hand written inlines special cases which Argument Clinic has no way, currently, of knowing about. The slowdown is ~20% on microbenchmarks. I built a prototype I call "pyspec" which would make these inlnining critical special cases visible to AC as well as available to pieces like the JIT. The core is to use plain python to write theiftree to get to C functions then lower that to C. That makes it so AC no longer reuqires the custom language/parser. Nothing revolutionary in the python ecosystems but I think has some nice side-effects.This currently only supports static types. GH-157968 makes it support non-static types. That is necessary to support Decimal. That makes both heap and static types supported and tested. It would be nice to get reviewed as it changes the asserts in generated code a little.
As a demonstration of speedup and applying to new types I put together GH-158891 for
bytesandbytearray. That turns on one of the core things I hope to enable with this work: minimal copybytes(bytearray(bytes(bytearray(b'123'))). Before Vectorcall there were references in arguments so every constructor had to copy. Now there is a uniquely referenced temporaty where we can "pass along" the storage avoiding copies.vstinner commentedon Oct 13, 2026
What is missing from AC generated code to get the same speed?
cmaloney commentedon Oct 13, 2026
AC needs to be able to direct dispatch / inline the "special" cases of 0, 1, and 2:
cpython/Objects/longobject.c
Lines 6529 to 6549 in 64bad23
In the first iteration I called this
zero_arg,one_argetc. To solve that I'm hoping to make the information available both to Argument Clinic and projects like the JIT. That was too specialized of infra in a lot of ways.Currently functions like
bytes_new_implare fully opaque. I'm hoping to make them be less opaque by giving the base decision tree in some Python syntax. Need some help from core devs what is preferred (an if tree like existing code, match statements, overloads, ...). That keeps to very basic operations and can lower directly from AST -> C as well as building an "overload" table that the JIT can potentially consume.My prototype of a simple stub: https://github.com/cmaloney/cpython/blob/7abb5c4b7d1d9925e837d14205c6658ef97ae051/Objects/pyspec/bytesobject.py#L139-L155
And the full bytes
__new__: https://github.com/cmaloney/cpython/blob/7abb5c4b7d1d9925e837d14205c6658ef97ae051/Objects/pyspec/bytesobject.py#L69-L122The hope is we can have one core definition which we can efficiently codegen to the set of functions we need + metadata that says "single unique bytearray arg -> use this C function directly". That should reduce the number of instructions needed while keeping things maintainable. Argument for pushing towards Python syntax is to make it look just like everything else the JIT must optimize / less special cases and less custom parsers.
gh-87613: Use AC vectorcall for bytes and bytearray (#158891)