Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Reduce overhead to run one iteration of the asyncio event loop #110733

Closed
Labels
@bdraco

Description

@bdraco

Feature or enhancement

Proposal:

Consider the following use case:

An asyncio event loop where the majority of the handles being run have a very small run time because they are decoding Bluetooth or Zigbee packets.

In this case_run_once becomes a bit of a bottleneck. In production, themin andmax calls to find the timeout represent a significant portion of the run time. We can increase the number of packets that can be processed per second by switching themin andmax to a simple> and< check.

importheapqimporttimeitfromasyncioimporteventsfromasyncio.base_eventsimport (_MIN_CANCELLED_TIMER_HANDLES_FRACTION,_MIN_SCHEDULED_TIMER_HANDLES,MAXIMUM_SELECT_TIMEOUT,BaseEventLoop,_format_handle,logger,)fromtimeimportmonotonicclassFakeSelector:defselect(self,timeout):"""Wait for I/O events."""fake_selector=FakeSelector()original_loop=BaseEventLoop()original_loop._selector=fake_selectororiginal_loop._process_events=lambdaevents:Nonetimer=events.TimerHandle(monotonic()+100000,lambda:None, ("any",),original_loop,None)timer._scheduled=Trueheapq.heappush(original_loop._scheduled,timer)classOptimizedBaseEventLoop(BaseEventLoop):def_run_once(self):"""Run one full iteration of the event loop.        This calls all currently ready callbacks, polls for I/O,        schedules the resulting callbacks, and finally schedules        'call_later' callbacks.        """sched_count=len(self._scheduled)if (sched_count>_MIN_SCHEDULED_TIMER_HANDLESandself._timer_cancelled_count/sched_count>_MIN_CANCELLED_TIMER_HANDLES_FRACTION        ):# Remove delayed calls that were cancelled if their number# is too highnew_scheduled= []forhandleinself._scheduled:ifhandle._cancelled:handle._scheduled=Falseelse:new_scheduled.append(handle)heapq.heapify(new_scheduled)self._scheduled=new_scheduledself._timer_cancelled_count=0else:# Remove delayed calls that were cancelled from head of queue.whileself._scheduledandself._scheduled[0]._cancelled:self._timer_cancelled_count-=1handle=heapq.heappop(self._scheduled)handle._scheduled=Falsetimeout=Noneifself._readyorself._stopping:timeout=0elifself._scheduled:# Compute the desired timeout.timeout=self._scheduled[0]._when-self.time()iftimeout>MAXIMUM_SELECT_TIMEOUT:timeout=MAXIMUM_SELECT_TIMEOUTeliftimeout<0:timeout=0event_list=self._selector.select(timeout)self._process_events(event_list)# Needed to break cycles when an exception occurs.event_list=None# Handle 'later' callbacks that are ready.end_time=self.time()+self._clock_resolutionwhileself._scheduled:handle=self._scheduled[0]ifhandle._when>=end_time:breakhandle=heapq.heappop(self._scheduled)handle._scheduled=Falseself._ready.append(handle)# This is the only place where callbacks are actually *called*.# All other places just add them to ready.# Note: We run all currently scheduled callbacks, but not any# callbacks scheduled by callbacks run this time around --# they will be run the next time (after another I/O poll).# Use an idiom that is thread-safe without using locks.ntodo=len(self._ready)foriinrange(ntodo):handle=self._ready.popleft()ifhandle._cancelled:continueifself._debug:try:self._current_handle=handlet0=self.time()handle._run()dt=self.time()-t0ifdt>=self.slow_callback_duration:logger.warning("Executing %s took %.3f seconds",_format_handle(handle),dt                        )finally:self._current_handle=Noneelse:handle._run()handle=None# Needed to break cycles when an exception occurs.new_loop=OptimizedBaseEventLoop()new_loop._selector=fake_selectornew_loop._process_events=lambdaevents:Nonetimer=events.TimerHandle(monotonic()+100000,lambda:None, ("any",),new_loop,None)timer._scheduled=Trueheapq.heappush(new_loop._scheduled,timer)new_time=timeit.timeit("loop._run_once()",number=1000000,globals={"loop":new_loop},)print("new: %s"%new_time)original_time=timeit.timeit("loop._run_once()",number=1000000,globals={"loop":original_loop},)print("original: %s"%original_time)
new: 0.36033158400096original: 0.4667800000170246

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:

No response

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


      [8]ページ先頭

      ©2009-2025 Movatter.jp