6

LAN 経由で機能するサーバー用に Python で記述されたクライアントがあります。アルゴリズムの一部はソケット読み取りを集中的に使用しており、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''

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''

recvこのコードでは、ソケットからの接続が最適に見えます。しかし、今next_intnext_floatは 2 番目のボトルネックになり、解凍するだけで呼び出しごとに約 1 ミリ秒 (3000 CPU サイクル) かかります。C++ のように高速化することはできますか?

4

1 に答える 1

3

andから中間文字列を作成し、一度に 1 つの値のみをアンパックするため、最新のボトルネックはnext_intandにあります。next_floatbytearray

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
于 2012-05-26T04:20:03.933 に答える