Skip to content

[mypyc] Add am_send slot and use PyIter_Send for await/yield from - #21979

Merged
JukkaL merged 9 commits into
masterfrom
mypyc-async-3
Sep 14, 2026
Merged

JukkaL merged 9 commits into
masterfrom
mypyc-async-3

Conversation

@JukkaL

@JukkaL JukkaL commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

This speeds up async defs and generators when crossing dynamic/static boundaries.

Generated generator and coroutine classes now install an am_send slot that
adapts __mypyc_generator_helper__ to CPython's C send protocol. Yielded values
use PYGEN_NEXT, normal returns use PYGEN_RETURN and an out parameter, and errors
use PYGEN_ERROR.

CPyIter_Send now wraps PyIter_Send and returns normal completion through an out
parameter. The generic await/yield from path uses it for the initial advance
and ordinary resumed sends, avoiding StopIteration overhead. Statically known
native children retain the direct generator-helper fast path. asyncio's C Task
also reaches generated coroutines through the new am_send slot.

In a microbenchmark on CPython 3.14, median gains are 25-26% for dynamically
typed immediate awaits, 33-37% for dynamically typed suspended awaits,
20% for one statically typed suspended await, and 26-30% for two.
In realistic asyncio workloads gains will be much smaller.

I used heavy coding agent assist, but iterated on results quite a lot (mostly tests and
comments).

Generated generator and coroutine classes now install an am_send slot that
adapts __mypyc_generator_helper__ to CPython's C send protocol. Yielded values
use PYGEN_NEXT, normal returns use PYGEN_RETURN and an out parameter, and errors
use PYGEN_ERROR. StopIteration escaping the helper also maps to PYGEN_RETURN,
preserving the known mypyc bug where an explicit StopIteration in a generator
body is treated as normal completion.

CPyIter_Send now wraps PyIter_Send and returns normal completion through an out
parameter. The generic await/yield from path uses it for the initial advance
and ordinary resumed sends, avoiding StopIteration overhead. Statically known
native children retain the direct generator-helper fast path. asyncio's C Task
also reaches generated coroutines through the new am_send slot.

Across 8 interleaved benchmark rounds on CPython 3.14, median gains are 25-26%
for dynamically typed immediate awaits, 33-37% for dynamically typed suspended
awaits, 20% for one statically typed suspended await, and 26-30% for two.
Native coroutine chains of length 5 and 20 improve by about 10% and 3-5%.
Immediate static awaits are unchanged, and uvloop results are inconclusive.
Since the baseline already optimizes statically known resumed children,
chain20 improves from 1.75x to 1.83x interpreted speed with the GIL and from
1.63x to 1.67x with free threading.
@JukkaL
JukkaL merged commit eff901b into master Sep 14, 2026
18 checks passed
@JukkaL
JukkaL deleted the mypyc-async-3 branch September 14, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants