我有一个用 Python 编写的服务器客户端,它通过 LAN 运行。该算法的某些部分使用套接字密集读取,其执行速度比用 C++ 编写的“几乎相同”慢约 3-6 倍。有哪些解决方案可以使 Python 套接字读取速度更快? 我实现了一些简单的缓冲,我使用套接字的类如下所示:
import socket
import struct
class Sock():
def __init__(self):
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.recv_buf = b''
self.send_buf = b''
def connect(self):
self.s.connect(('127.0.0.1', 6666))
def close(self):
self.s.close()
def recv(self, lngth):
while len(self.recv_buf) < lngth:
self.recv_buf += self.s.recv(lngth - len(self.recv_buf))
res = self.recv_buf[-lngth:]
self.recv_buf = self.recv_buf[:-lngth]
return res
def next_int(self):
return struct.unpack("i", self.recv(4))[0]
def next_float(self):
return struct.unpack("f", self.recv(4))[0]
def write_int(self, i):
self.send_buf += struct.pack('i', i)
def write_float(self, f):
self.send_buf += struct.pack('f', f)
def flush(self):
self.s.sendall(self.send_buf)
self.send_buf = b''
P.S.:分析还表明大部分时间都花在读取套接字上。编辑:
因为数据是在已知大小的块中接收的,所以我可以一次读取整个块。 所以我将代码更改为:
class Sock():
def __init__(self):
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.send_buf = b''
def connect(self):
self.s.connect(('127.0.0.1', 6666))
def close(self):
self.s.close()
def recv_prepare(self, cnt):
self.recv_buf = bytearray()
while len(self.recv_buf) < cnt:
self.recv_buf.extend(self.s.recv(cnt - len(self.recv_buf)))
self.recv_buf_i = 0
def skip_read(self, cnt):
self.recv_buf_i += cnt
def next_int(self):
self.recv_buf_i += 4
return struct.unpack("i", self.recv_buf[self.recv_buf_i - 4:self.recv_buf_i])[0]
def next_float(self):
self.recv_buf_i += 4
return struct.unpack("f", self.recv_buf[self.recv_buf_i - 4:self.recv_buf_i])[0]
def write_int(self, i):
self.send_buf += struct.pack('i', i)
def write_float(self, f):
self.send_buf += struct.pack('f', f)
def flush(self):
self.s.sendall(self.send_buf)
self.send_buf = b''
recv
看起来是最佳的。但现在
next_int
和 next_float
成为第二个瓶颈,它们每次调用大约需要 1 毫秒(3000 个 CPU 周期)来解包。是否有可能让它们更快,就像在 C++ 中一样? next_int
和
next_float
,因为您从 bytearray
创建中间字符串,并且一次只解压一个值。struct
模块有一个
unpack_from
,它需要一个缓冲区和一个偏移量。 这更有效,因为不需要从您的 bytearray
:创建中间字符串
def next_int(self):
self.recv_buf_i += 4
return struct.unpack_from("i", self.recv_buf, self.recv_buf_i-4)[0]
此外,
struct
模块可以一次解包多个值。 目前,您可以从 Python 调用 C(通过模块)来获取每个值。 减少调用它的次数并让它在每次调用中完成更多的工作会得到更好的服务:
def next_chunk(self, fmt): # fmt can be a group such as "iifff"
sz = struct.calcsize(fmt)
self.recv_buf_i += sz
return struct.unpack_from(fmt, self.recv_buf, self.recv_buf_i-sz)
如果您知道
fmt
始终是 4 字节整数和浮点数,您可以将
struct.calcsize(fmt)
替换为 4 * len(fmt)
。最后,作为一个偏好问题,我认为这样读起来更清晰:
def next_chunk(self, fmt):
sz = struct.calcsize(fmt)
chunk = struct.unpack_from(fmt, self.recv_buf, self.recv_buf_i)
self.recv_buf_i += sz
return chunk
不幸的是,Python 不允许轻松修改网络操作,我开发了一个解决方案来加速 Python 的网络堆栈,但它不是公开的,并且需要修补 Python 应用程序。