Быстрее сокетов в Python

У меня есть клиент, написанный на Python для сервера, который работает через локальную сеть. Некоторая часть алгоритма интенсивно использует чтение сокетов и работает примерно в 3-6 раз медленнее, чемпочти такой же написанный на C ++. Какие существуют решения для ускорения чтения сокетов 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''

П.С .: Профилирование также показывает, что большую часть времени проводит чтение сокетов.

Edit: Поскольку данные принимаются в блоках с известным размером, я могу прочитать весь блок сразу. Поэтому я изменил свой код следующим образом:

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 циклов ЦП) на вызов. Можно ли сделать их быстрее, как в C ++?

 kichik24 мая 2012 г., 19:49
Когда вы говорите, что чтение профилей занимает больше времени при профилировании, вы имеете в видуself.s.recv или жеSock.recv?
 Francis Avila24 мая 2012 г., 20:05
Вы делаете немного байтового копирования. Может быть, попробуйте использоватьbytearray(), socket.recv_into, а такжеstruct.pack_into выжать несколько циклов?
 Steven Rumbalski24 мая 2012 г., 20:21
@chrsanya: Thedocs claim что вы не получите больше данных, чем запрашиваете: & quot; максимальный объем данных, которые должны быть получены одновременно, указан в bufsize. & quot;
 Steven Rumbalski24 мая 2012 г., 20:18
Было бы неплохо увидеть аналогичную версию C ++. Кроме того, у меня сложилось впечатление, что вы читаете очень маленькими порциями, и это звучит неоптимально. Кроме того, операции, которые добавляются в строку или всплывают с начала строки, действительно неэффективны в python, потому что неизменяемые строки вызывают создание новых строк каждый раз.
 Steven Rumbalski24 мая 2012 г., 20:32
@chersanya: я почти ничего не знаю о сокетах, но в документах, как представляется, содержится предположение, что при обычном использовании они читаются гораздо крупнее: & quot;Note: Для лучшего соответствия аппаратным и сетевым реалиям значение bufsize должно быть относительно небольшим, равным 2, например 4096. & quot; Если сокету требуется отдельное соединение по сети для каждого блока, который он читает, я мог бы увидеть, как быстро складывается время. Я думаю, что если вы будете читать большими кусками, то ваш метод буферизации станет узким местом.

Ответы на вопрос(1)

Решение Вопроса

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

Ваш ответ на вопрос