Skip to content

Consider broadening _asyncio _call_soon fast path beyond exact built-in loop types #153534

Description

@KRRT7

_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

Metadata

Metadata

Assignees

No one assigned

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions