Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce asyncio heapq overhead #122881

Closed
bdraco opened this issue Aug 10, 2024 · 3 comments
Closed

Reduce asyncio heapq overhead #122881

bdraco opened this issue Aug 10, 2024 · 3 comments
Labels
performance Performance or resource usage topic-asyncio type-feature A feature request or enhancement

Comments

@bdraco
Copy link
Contributor

bdraco commented Aug 10, 2024

Feature or enhancement

Proposal:

The heapq calls in base_events.py represents quite a bit of asyncio scheduling overhead because they have to run __lt__ quite often in TimerHandle

def __lt__(self, other):

heapq.heapify(new_scheduled)

heapify

heapq.heappush(self._scheduled, timer)

heappush

handle = heapq.heappop(self._scheduled)

heappop

Avoiding running __lt__ can speed up processing call_ats by ~10%

Has this already been discussed elsewhere?

This is a minor feature, which does not need previous discussion elsewhere

Links to previous discussion of this feature:

Wrapping TimerHandle in a tuple starting with when avoids the __lt__ call. Thank you to whoever wrote the heapq docs for help getting there https://docs.python.org/3/library/heapq.html#basic-examples
6f80b4c

Example benchmark

from asyncio import TimerHandle
import heapq
import timeit


def callback():
    """This is the callback function that will be called when the timer expires."""


class MockLoop:
    def get_debug(self):
        return False


loop = MockLoop()


def heap_tuple():
    scheduled = []
    when = 1

    for _ in range(100):
        when += 1
        handle = TimerHandle(when, callback, (), loop)
        heapq.heappush(scheduled, (when, handle))

    while scheduled:
        when, handle = heapq.heappop(scheduled)


def heap_handle():
    scheduled = []
    when = 1

    for _ in range(100):
        when += 1
        handle = TimerHandle(when, callback, (), loop)
        heapq.heappush(scheduled, handle)

    while scheduled:
        handle = heapq.heappop(scheduled)


print("wrap when, TimerHandle in tuple", timeit.timeit(heap_tuple))
print("bare TimerHandle", timeit.timeit(heap_handle))
% python3 bench/timer_handle_heap.py
wrap when, TimerHandle in tuple 34.082984749999014
bare TimerHandle 49.678519583001616

Linked PRs

@bdraco bdraco added the type-feature A feature request or enhancement label Aug 10, 2024
@github-project-automation github-project-automation bot moved this to Todo in asyncio Aug 10, 2024
@Eclips4 Eclips4 added the performance Performance or resource usage label Aug 10, 2024
@bdraco
Copy link
Contributor Author

bdraco commented Aug 10, 2024

call_at benchmark with the change in #122882

import asyncio
import timeit


def run():
    asyncio.run(call_at())


async def call_at():
    loop = asyncio.get_running_loop()
    when = loop.time()
    future = loop.create_future()

    def callback():
        """Callback function."""

    def done():
        """Done function."""
        future.set_result(None)

    for _ in range(100):
        when += 0.00000001
        loop.call_at(when, callback)

    loop.call_at(when, done)
    await future


print("call_at_benchmark", timeit.timeit(run))
before: call_at_benchmark 252.9052056250075
after: call_at_benchmark 228.09943808300886

@rhettinger
Copy link
Contributor

Wow, this was an excellent problem analysis.

Your recommended solution seems reasonable to me.

@vstinner
Copy link
Member

I close the issue since the related PR was closed.

@vstinner vstinner closed this as not planned Won't fix, can't repro, duplicate, stale Sep 23, 2024
@github-project-automation github-project-automation bot moved this from Todo to Done in asyncio Sep 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
performance Performance or resource usage topic-asyncio type-feature A feature request or enhancement
Projects
Status: Done
Development

No branches or pull requests

4 participants