Enchufes más rápidos en Python

Tengo un cliente escrito en Python para un servidor, que funciona a través de LAN. Una parte del algoritmo utiliza la lectura de socket de forma intensiva y se está ejecutando de 3 a 6 veces más lento que casi el mismo escrito en C ++. ¿Qué soluciones existen para hacer que el socket Python lea más rápido?

Tengo implementado un búfer simple y mi clase para trabajar con sockets se parece a esto:

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

PD: la creación de perfiles también muestra que la mayor parte del tiempo se dedica a leer sockets.

Edición: dado que los datos se reciben en bloques con un tamaño conocido, puedo leer todo el bloque a la vez. Así que he cambiado mi código a esto:

 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 de un socket se ve óptima en este código. Pero ahora next_int y next_float convirtieron en el segundo cuello de botella, toman aproximadamente 1 mseg (3000 ciclos de CPU) por llamada solo para desempaquetar. ¿Es posible hacerlos más rápidos, como en C ++?

Su último cuello de botella se encuentra en next_int y next_float porque crea cadenas intermedias desde el bytearray y porque solo desempaqueta un valor a la vez.

El módulo de struct tiene un unpack_from que toma un búfer y un desplazamiento. Esto es más eficiente porque no hay necesidad de crear una cadena intermedia desde su bytearray :

 def next_int(self): self.recv_buf_i += 4 return struct.unpack_from("i", self.recv_buf, self.recv_buf_i-4)[0] 

Además, el módulo de struct puede descomprimir más de un valor a la vez. Actualmente, usted llama de Python a C (a través del módulo) para cada valor. Lo atenderán mejor si lo llama menos veces y le permite hacer más trabajo en cada llamada:

 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) 

Si sabe que fmt siempre será de 4 bytes enteros y flotantes, puede reemplazar struct.calcsize(fmt) con 4 * len(fmt) .

Finalmente, como cuestión de preferencia, creo que esto se lee de manera más limpia:

 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