Closed
Description
split out from #106555 (comment)
The current EpollSelector
can be sped up a bit. This makes quite a difference when there are 100000+ iterations of the event loop per minute (the use case being receiving bluetooth data from multiple sources) since selectors have to run every iteration.
original: 11.831302762031555
new: 9.579423972172663
import timeit
import math
import select
import os
from selectors import EpollSelector, EVENT_WRITE, EVENT_READ
class OriginalEpollSelector(EpollSelector):
def select(self, timeout=None):
if timeout is None:
timeout = -1
elif timeout <= 0:
timeout = 0
else:
# epoll_wait() has a resolution of 1 millisecond, round away
# from zero to wait *at least* timeout seconds.
timeout = math.ceil(timeout * 1e3) * 1e-3
# epoll_wait() expects `maxevents` to be greater than zero;
# we want to make sure that `select()` can be called when no
# FD is registered.
max_ev = max(len(self._fd_to_key), 1)
ready = []
try:
fd_event_list = self._selector.poll(timeout, max_ev)
except InterruptedError:
return ready
for fd, event in fd_event_list:
events = 0
if event & ~select.EPOLLIN:
events |= EVENT_WRITE
if event & ~select.EPOLLOUT:
events |= EVENT_READ
key = self._key_from_fd(fd)
if key:
ready.append((key, events & key.events))
return ready
NOT_EPOLLIN = ~select.EPOLLIN
NOT_EPOLLOUT = ~select.EPOLLOUT
class NewEpollSelector(EpollSelector):
def select(self, timeout=None):
if timeout is None:
timeout = -1
elif timeout <= 0:
timeout = 0
else:
# epoll_wait() has a resolution of 1 millisecond, round away
# from zero to wait *at least* timeout seconds.
timeout = math.ceil(timeout * 1e3) * 1e-3
# epoll_wait() expects `maxevents` to be greater than zero;
# we want to make sure that `select()` can be called when no
# FD is registered.
max_ev = len(self._fd_to_key) or 1
ready = []
try:
fd_event_list = self._selector.poll(timeout, max_ev)
except InterruptedError:
return ready
fd_to_key = self._fd_to_key
for fd, event in fd_event_list:
key = fd_to_key.get(fd)
if key:
ready.append(
(
key,
(
(event & NOT_EPOLLIN and EVENT_WRITE)
| (event & NOT_EPOLLOUT and EVENT_READ)
)
& key.events,
)
)
return ready
original_epoll = OriginalEpollSelector()
new_epoll = NewEpollSelector()
for _ in range(512):
r, w = os.pipe()
os.write(w, b"a")
original_epoll.register(r, EVENT_READ)
new_epoll.register(r, EVENT_READ)
original_time = timeit.timeit(
"selector.select()",
number=100000,
globals={"selector": original_epoll},
)
new_time = timeit.timeit(
"selector.select()",
number=100000,
globals={"selector": new_epoll},
)
print("original: %s" % original_time)
print("new: %s" % new_time)