Faster sockets in Python

2024/10/6 20:25:35

I have a client written in Python for a server, which functions through LAN. Some part of the algorithm uses socket reading intensively and it is executing about 3-6 times slower, than almost the same one written in C++. What solutions exist for making Python socket reading faster?

I have some simple buffering implemented, and my class for working with sockets looks like this:

import socket
import structclass 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 resdef 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''

P.S.: profiling also shows that the majority of time is spent reading sockets.

Edit: Because data is received in blocks with known size, I can read the whole block at once. So I've changed my code to this:

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 = 0def skip_read(self, cnt):self.recv_buf_i += cntdef next_int(self):self.recv_buf_i += 4return struct.unpack("i", self.recv_buf[self.recv_buf_i - 4:self.recv_buf_i])[0]def next_float(self):self.recv_buf_i += 4return 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'ing from socket looks optimal in this code. But now 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++?

Answer

Your latest bottleneck is in next_int and next_float because you create intermediate strings from the bytearray and because you only unpack one value at a time.

The struct module has an unpack_from that takes a buffer and an offset. This is more efficient because there is no need to create an intermediate string from your bytearray:

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

Additionally, struct module can unpack more than one value at a time. Currently, you call from Python to C (via the module) for each value. You would be better served by calling it fewer times and letting it do more work on each call:

def next_chunk(self, fmt): # fmt can be a group such as "iifff" sz = struct.calcsize(fmt) self.recv_buf_i += szreturn struct.unpack_from(fmt, self.recv_buf, self.recv_buf_i-sz)

If you know that fmt will always be 4 byte integers and floats you can replace struct.calcsize(fmt) with 4 * len(fmt).

Finally, as a matter of preference I think this reads more cleanly:

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 += szreturn chunk
https://en.xdnf.cn/q/70329.html

Related Q&A

Python gmail api send email with attachment pdf all blank

I am using python 3.5 and below code is mostly from the google api page... https://developers.google.com/gmail/api/guides/sending slightly revised for python 3.xi could successfully send out the email …

how to find height and width of image for FileField Django

How to find height and width of image if our model is defined as followclass MModel:document = FileField()format_type = CharField()and image is saved in document then how we can find height and width o…

Given a pickle dump in python how to I determine the used protocol?

Assume that I have a pickle dump - either as a file or just as a string - how can I determine the protocol that was used to create the pickle dump automatically? And if so, do I need to read the entir…

Get First element by the recent date of each group

I have following model in djangoBusiness ID Business Name Business Revenue DateHere is the sample data:Business ID | Business Name | Business Revenue | Date 1 B1 1000 …

remote: ImportError: No module named gitlab

I wrote gitlab hook with python. And added to post-receive hooks in gitlab server. When i push to remote origin server from my laptop, i get following error. But it works when i run script manually in …

Using an Access database (.mdb) with Python on Ubuntu [duplicate]

This question already has answers here:Working with an Access database in Python on non-Windows platform (Linux or Mac)(5 answers)Closed 7 years ago.Im trying to use pyodbc to access a .mdb on Ubuntu. …

Pandas Grouper by weekday?

I have a pandas dataframe where the index is the date, from year 2007 to 2017.Id like to calculate the mean of each weekday for each year. I am able to group by year: groups = df.groupby(TimeGrouper(A)…

Can I move the pygame game window around the screen (pygame)

In the game Im making, Im trying to move the window around the screen for a mini game (dont ask) and Ive tried what I saw own threads and only found 1x = 100 y = 0 import os os.environ[SDL_VIDEO_WINDOW…

mocking a function within a class method

I want to mock a function which is called within a class method while testing the class method in a Django project. Consider the following structure: app/utils.py def func():...return resp # outcome i…

After resizing an image with cv2, how to get the new bounding box coordinate

I have an image of size 720 x 1280, and I can resize it to 256 x 256 like thisimport cv2 img = cv2.imread(sample_img.jpg) img_small = cv2.resize(img, (256, 256), interpolation=cv2.INTER_CUBIC)Say I hav…