_asyncio currently gets a measurable win by using the loop's private
_call_soon() helper on hot task and future scheduling paths.
Right now that optimization is limited to exact built-in asyncio loop types.
That is safe, but it may be more conservative than necessary.
I ran a small benchmark matrix comparing:
- exact built-in asyncio loops
- subclasses of stdlib
BaseEventLoop with no call_soon override
- loops with Python-level
call_soon customization
The consistent result was that subclassing by itself was not the expensive
case. The slower case was Python-level call_soon customization.
That suggests there may be room to broaden the _call_soon() fast path to
safe BaseEventLoop subclasses while still preserving behavior for custom
loop implementations.
One candidate rule is:
- allow the fast path for stdlib
BaseEventLoop subclasses
- require that the subclass does not define
call_soon
- require that the loop instance does not shadow
call_soon
- otherwise fall back to the public
call_soon() path
I prototyped that rule locally and added regression coverage for:
- subclass loops that leave
call_soon unchanged
- instance-level
loop.call_soon = ... monkeypatching
- existing override behavior
The local asyncio slice passed:
./python.exe -m test \
test_asyncio.test_base_events \
test_asyncio.test_events \
test_asyncio.test_tasks \
test_asyncio.test_futures
Local benchmark snapshot
| Benchmark |
exact loop |
subclass loop |
call_soon override |
CPython PGO time_future_wakeup |
41.6±0.9ms |
42.2±1ms |
51.7±1ms |
CPython PGO time_task_spawn |
80.4±2ms |
77.3±1ms |
98.2±0.6ms |
The cleanest signal came from the PGO release build: subclass-without-override
stayed effectively tied with the exact-loop case, while Python-level
call_soon customization remained clearly slower. The plain release build had
the same overall shape for future_wakeup, but task_spawn_subclass_loop was
noisier there.
Open questions
- Is broadening this fast path desirable enough to justify the extra guard
complexity in _asynciomodule.c?
- Is there a simpler way to express the safety boundary than checking both type
and instance call_soon shadowing?
- Would release-build benchmarking be enough to make the case, or is broader
event-loop coverage needed first?
Linked PRs
_asynciocurrently gets a measurable win by using the loop's private_call_soon()helper on hot task and future scheduling paths.Right now that optimization is limited to exact built-in asyncio loop types.
That is safe, but it may be more conservative than necessary.
I ran a small benchmark matrix comparing:
BaseEventLoopwith nocall_soonoverridecall_sooncustomizationThe consistent result was that subclassing by itself was not the expensive
case. The slower case was Python-level
call_sooncustomization.That suggests there may be room to broaden the
_call_soon()fast path tosafe
BaseEventLoopsubclasses while still preserving behavior for customloop implementations.
One candidate rule is:
BaseEventLoopsubclassescall_sooncall_sooncall_soon()pathI prototyped that rule locally and added regression coverage for:
call_soonunchangedloop.call_soon = ...monkeypatchingThe local asyncio slice passed:
./python.exe -m test \ test_asyncio.test_base_events \ test_asyncio.test_events \ test_asyncio.test_tasks \ test_asyncio.test_futuresLocal benchmark snapshot
call_soonoverridetime_future_wakeup41.6±0.9ms42.2±1ms51.7±1mstime_task_spawn80.4±2ms77.3±1ms98.2±0.6msThe cleanest signal came from the PGO release build: subclass-without-override
stayed effectively tied with the exact-loop case, while Python-level
call_sooncustomization remained clearly slower. The plain release build hadthe same overall shape for
future_wakeup, buttask_spawn_subclass_loopwasnoisier there.
Open questions
complexity in
_asynciomodule.c?and instance
call_soonshadowing?event-loop coverage needed first?
Linked PRs
_asyncio_call_soonfast path to safeBaseEventLoopsubclasses #153535