From 5796be36f8f177bdca3d9dfc41c02bf687b7ed44 Mon Sep 17 00:00:00 2001 From: Martin Kirchgessner Date: Sun, 20 Feb 2022 15:47:49 +0100 Subject: [PATCH 1/2] bpo-46740: enhance telnetlib's buffer transfers This change leverages the very low frequency of IAC (control) characters: instead of reading/interpreting/appending per character, it's much faster to search them linearly (if any), then copy the data slice to the cooked queue. This was inspired by point 2) in this very old Python-dev topic https://python-dev.python.narkive.com/A86fs3PG/patch-to-telnetlib-py and yields a 5x speedup too. Also calling `socket.recv` with the currently recommended buffer size, 4096. --- Lib/telnetlib.py | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/Lib/telnetlib.py b/Lib/telnetlib.py index ae88ea594746fd..d066421ba0e035 100644 --- a/Lib/telnetlib.py +++ b/Lib/telnetlib.py @@ -431,18 +431,20 @@ def process_rawq(self): buf = [b'', b''] try: while self.rawq: - c = self.rawq_getchar() if not self.iacseq: - if c == theNULL: - continue - if c == b"\021": - continue - if c != IAC: - buf[self.sb] = buf[self.sb] + c - continue + slice = self._rawq_getslice() + if slice: + buf[self.sb] = buf[self.sb] + slice else: - self.iacseq += c + c = self.rawq_getchar() + if c == theNULL: + continue + elif c == b"\021": + continue + else: + self.iacseq += c elif len(self.iacseq) == 1: + c = self.rawq_getchar() # 'IAC: IAC CMD [OPTION only for WILL/WONT/DO/DONT]' if c in (DO, DONT, WILL, WONT): self.iacseq += c @@ -469,6 +471,7 @@ def process_rawq(self): # unless we did a WILL/DO before. self.msg('IAC %d not recognized' % ord(c)) elif len(self.iacseq) == 2: + c = self.rawq_getchar() cmd = self.iacseq[1:2] self.iacseq = b'' opt = c @@ -492,6 +495,28 @@ def process_rawq(self): self.cookedq = self.cookedq + buf[0] self.sbdataq = self.sbdataq + buf[1] + def _next_nonIAC_slice(self): + """Return next non-IAC characters from raw queue. + Assumes the caller checked the raw queue is not empty. + """ + next_i = self.irawq + max_i = len(self.rawq) + while next_i < max_i: + c = self.rawq[next_i] + if c == theNULL[0] or c == 0x11 or c == IAC[0]: + break + next_i += 1 + + if next_i == self.irawq: + return None + else: + slice = self.rawq[self.irawq:next_i] + self.irawq = next_i + if next_i == max_i: + self.rawq = b'' + self.irawq = 0 + return slice + def rawq_getchar(self): """Get next char from raw queue. @@ -522,7 +547,7 @@ def fill_rawq(self): self.irawq = 0 # The buffer size should be fairly small so as to avoid quadratic # behavior in process_rawq() above - buf = self.sock.recv(50) + buf = self.sock.recv(4096) self.msg("recv %r", buf) self.eof = (not buf) self.rawq = self.rawq + buf From 33c89c468b6186e49c07f9b4a7a06d55c127ef40 Mon Sep 17 00:00:00 2001 From: Martin Kirchgessner Date: Sun, 20 Feb 2022 16:46:17 +0100 Subject: [PATCH 2/2] copy-paste error --- Lib/telnetlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/telnetlib.py b/Lib/telnetlib.py index d066421ba0e035..78abecf561149c 100644 --- a/Lib/telnetlib.py +++ b/Lib/telnetlib.py @@ -432,7 +432,7 @@ def process_rawq(self): try: while self.rawq: if not self.iacseq: - slice = self._rawq_getslice() + slice = self._next_nonIAC_slice() if slice: buf[self.sb] = buf[self.sb] + slice else: