我有一个用 Python 编写的服务器客户端,它通过 LAN 运行。该算法的某些部分密集使用套接字读取,其执行速度比用 C++ 编写的几乎相同的 慢 3-6 倍。有哪些解决方案可以使 Python 套接字读取速度更快? 我实现了一些简单的缓冲,我的用于处理套接字的类如下所示:
P.S.:分析还显示大部分时间都花在读取套接字上。| ||编辑:
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''
因为数据是在已知大小的块中接收的,所以我可以立即读取整个块。 所以我将我的代码更改为:
'从套接字在这段代码中看起来是最佳的。但现在 和
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
成为第二个瓶颈,每次调用需要大约 1 毫秒(3000 个 CPU 周期)来解包。是否有可能让它们更快,就像在 C++ 中一样?
next_int
and
next_float
became the second bottleneck, they take about 1 msec (3000 CPU cycles) per call just to unpack. Is it possible to make them faster, like in C++?
好的,让我们探讨一下如何加快你的 Python 套接字代码的速度。你做了一些重要的观察,并且你走在正确的轨道上!这里有一个结合你的洞察力和一些额外技巧的细分:
你已经做过的,而且工作得很好的
-
缓冲: 你的
Sock
类中的recv_buf
是一个 优秀的 优化。它通过最小化从套接字读取的系统调用次数来极大地提高性能。系统调用会产生开销,因此将它们成批读取是关键。 -
预先读取(
recv_prepare
): 你知道你的数据块的大小,这是一个巨大的优势。通过提前读取整个块,你进一步减少了开销并提高了数据局部性。
让我们优化
next_int
和
next_float
你的分析表明
struct.unpack
成为了瓶颈。这是有道理的,因为它在幕后进行了一些工作(数据复制、类型转换等)。以下是如何解决这个问题的方法:
-
内存视图(
memoryview
): 内存视图提供了一种在不复制数据的情况下访问字节缓冲区的方法。尝试一下:```python def next_int(self): self.recv_buf_i += 4 return struct.unpack("i", memoryview(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", memoryview(self.recv_buf)[self.recv_buf_i-4:self.recv_buf_i])[0] ```
-
NumPy(如果你正在处理数值数据): 如果你正在进行大量的数值处理,NumPy 是你最好的朋友。它可以用优化的 C 代码进行解包,这比纯 Python 快得多。
```python import numpy as np
def recv_prepare(self, cnt): self.recv_buf = bytearray() # ... (你的读取逻辑)
# 如果你知道数据的类型,指定 dtype(例如 '<f4' 用于小端浮点数) self.recv_view = np.frombuffer(self.recv_buf, dtype=np.float32) self.recv_buf_i = 0
def next_float(self): val = self.recv_view[self.recv_buf_i] self.recv_buf_i += 1 return val ```
其他需要考虑的事项
- 网络: 虽然你专注于套接字代码,但请记住,网络本身可能是瓶颈。确保你的网络设置已针对 LAN 环境进行了优化(例如,千兆以太网)。
- 算法: 如果在进行了这些优化之后,你仍然需要更多的速度,则可能需要重新访问你算法的 Python 部分。C++ 可以 更快,但通常情况下,精心编写的 Python 可以非常高效。
- Cython 或 Numba: 对于性能至关重要的代码部分,你可以考虑使用 Cython(编写可以编译为 C 扩展的 Python 超集)或 Numba(使用即时编译来加速 Python 函数)。
概要
通过使用
memoryview
或 NumPy 优化解包,你应该会看到性能得到显著提升。持续分析你的代码将帮助你确定剩余的瓶颈。祝你好运,玩得开心!