Python中更快的套接字

apl*_*vin 5 python sockets performance python-3.x

我有一个用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''
Run Code Online (Sandbox Code Playgroud)

PS:剖析也表明大部分时间花在阅读套接字上.

编辑:因为数据是以已知大小的块接收的,所以我可以一次读取整个块.所以我把我的代码更改为:

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''
Run Code Online (Sandbox Code Playgroud)

recv来自套接字在此代码中看起来是最佳的.但现在next_intnext_float成为第二个瓶颈,他们每次通话需要大约1毫秒(3000个CPU周期)才能打开包装.是否有可能使它们更快,就像在C++中一样?

Ste*_*ski 4

您最新的瓶颈在于next_intand ,next_float因为您从bytearrayand 创建中间字符串,因为您一次只解包一个值。

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]
Run Code Online (Sandbox Code Playgroud)

此外,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)
Run Code Online (Sandbox Code Playgroud)

如果您知道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
Run Code Online (Sandbox Code Playgroud)