You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Reduce overhead to add/remove asyncio readers and writers #106527
Interesting. Have you measured your alternative? Is it faster? I'm a little surprised that calling .get_map().get(key) is slower than calling .get_key(key) plus raising and catching an exception -- that would suggest a function call is faster than raising an catching an exception.
Maybe we would do better trying to make exception handling faster? (I guess this code is unusual because in the common case the exception gets raised, so our "zero-overhead exceptions" don't help, since it's only zero overhead when it doesn't get raised.)
The flames disappear from the py-spy after the change. I also did a cProfile and its faster. This isn't a great comparison though as its doesn't quantify the actual performance improvement and only shows more of the real world impact. I'll see if I can put together something that benchmarks it
def__getitem__(self, fileobj):
fd=self._selector._fileobj_lookup(fileobj)
key=self._selector._fd_to_key.get(fd)
ifkeyisNone:
raiseKeyError("{!r} is not registered".format(fileobj))
returnkey
I'll wait for the review of the linked PR and pending the outcome there will open another PR for this. In the mean time I'll push the above code to production and run it for a bit.
That helps a bit as it avoids one except catch but on the add_reader case the last KeyError has to raise
It also looks like the selectors could remove _key_from_fd and replace it with a simple self._fd_to_key.get() unless _key_from_fd is a defined API promise. That's roughly a 10% reduction of run time of the code in selectors.py when running an asyncio event loop with ~10000 calls to select per minute since it doesn't have the extra function depth overhead.
That should also likely be another PR. Probably worth doing since its called in the select loop and this was on a mostly idle system.
Maybe we would do better trying to make exception handling faster? (I guess this code is unusual because in the common case the exception gets raised, so our "zero-overhead exceptions" don't help, since it's only zero overhead when it doesn't get raised.)
I think that would also be worthwhile, FWIW. I was quite surprised how much switching to LBYL idioms sped things up in #103318, largely because of how slow exception handling is.
(N.B. I don't think longterm goals like that should impact whether this proposed change is accepted or not.)
FWIW I think it is much better to optimize selectors module rather than asyncio in this case.
I agree that selectors are the primary source of the bottleneck I'm seeing. (nothing else is called 100000x per minute on busy systems besides _run_once)
The linked #106528 can use the new get implementation #106665 which will avoids the exceptions in the happy path of _add_reader which I think is still valuable on its own when you have a situation where readers are added and removed serval times per second.
Activity
gvanrossum commentedon Jul 7, 2023
Interesting. Have you measured your alternative? Is it faster? I'm a little surprised that calling
.get_map().get(key)is slower than calling.get_key(key)plus raising and catching an exception -- that would suggest a function call is faster than raising an catching an exception.Maybe we would do better trying to make exception handling faster? (I guess this code is unusual because in the common case the exception gets raised, so our "zero-overhead exceptions" don't help, since it's only zero overhead when it doesn't get raised.)
@kumaraditya303 Thoughts? The PR seems well thought out.
bdraco commentedon Jul 7, 2023
The flames disappear from the
py-spyafter the change. I also did a cProfile and its faster. This isn't a great comparison though as its doesn't quantify the actual performance improvement and only shows more of the real world impact. I'll see if I can put together something that benchmarks itbdraco commentedon Jul 7, 2023
I think this is close enough to benchmark the change:
gvanrossum commentedon Jul 7, 2023
Yeah, that’s acceptable.
bdraco commentedon Jul 8, 2023
_SelectorMapping.__getitem__has the same patternprobably something like this would offer the same speed up
cpython/Lib/selectors.py
Line 73 in da98ed0
I'll wait for the review of the linked PR and pending the outcome there will open another PR for this. In the mean time I'll push the above code to production and run it for a bit.
That helps a bit as it avoids one except catch but on the add_reader case the last KeyError has to raise
bdraco commentedon Jul 9, 2023
It also looks like the selectors could remove
_key_from_fdand replace it with a simpleself._fd_to_key.get()unless_key_from_fdis a defined API promise. That's roughly a 10% reduction of run time of the code inselectors.pywhen running an asyncio event loop with ~10000 calls toselectper minute since it doesn't have the extra function depth overhead.That should also likely be another PR. Probably worth doing since its called in the
selectloop and this was on a mostly idle system.rough profile (different points in time with similar workloads)

before:
after
AlexWaygood commentedon Jul 12, 2023
I think that would also be worthwhile, FWIW. I was quite surprised how much switching to LBYL idioms sped things up in #103318, largely because of how slow exception handling is.
(N.B. I don't think longterm goals like that should impact whether this proposed change is accepted or not.)
kumaraditya303 commentedon Jul 14, 2023
FWIW I think it is much better to optimize selectors module rather than
asyncioin this case.bdraco commentedon Jul 14, 2023
I agree that selectors are the primary source of the bottleneck I'm seeing. (nothing else is called 100000x per minute on busy systems besides
_run_once)More discussion on that in #106555
bdraco commentedon Jul 14, 2023
The linked #106528 can use the new get implementation #106665 which will avoids the exceptions in the happy path of
_add_readerwhich I think is still valuable on its own when you have a situation where readers are added and removed serval times per second.bdraco commentedon Jul 14, 2023
edit: sorry I screwed up and linked the wrong PR above. Should have been #106665
gh-106527: asyncio: optimize to add/remove readers and writers (#106528)
pythongh-106527: asyncio: optimize to add/remove readers and writers (p…
pythongh-106527: asyncio: optimize to add/remove readers and writers (p…
kumaraditya303 commentedon Jul 24, 2023
Thanks for working on this.