python3.6:socket.recv() 与 socket.recv_into() 性能对比

Posted

技术标签:

【中文标题】python3.6:socket.recv() 与 socket.recv_into() 性能对比【英文标题】:python3.6: socket.recv() vs socket.recv_into() performance 【发布时间】:2021-04-18 01:00:12 【问题描述】:

我一直在使用 python3.6 来捕获高速 udp 流,并尝试使用 socket.recv()socket.recv_into()。我预计recv_into() 会更快,因为每次读取数据包并将其附加到列表时,它都会直接复制到“preallocated”bytearray 而不是creating a new string。

我的测试场景是核心绑定的,我知道我正在丢弃一些数据包,并且通过setsockoptSO_RCVBUF 有一个大的套接字接收缓冲区大小。我还关闭了垃圾收集器以避免随机中断。

以下 sn-ps 具有类似的性能,这对我来说没有意义,并且想知道是否有人可以帮助指出我缺少的东西。谢谢!

pkts = []
while time.time() - t_start < 10.0:
    pkt = s.recv(2048)
    pkts.append(pkt)
num_recv_captured = len(pkts)

对比

buffer = bytearray(2048)

num_recv_into_captured = 0
while time.time() - t_start < 10.0:
    s.recv_into(buffer, 2048)
    num_recv_into_captured += 1

在这里,我看到num_recv_into_captured 在核心绑定场景中类似于num_recv_captured,但预计num_recv_into_captured 会更大一些。

【问题讨论】:

【参考方案1】:

性能测量非常困难。您看到的可能是由于您的测试方法存在问题,也可能是结果太接近而无法引起注意。

因此,首先查看您要比较的两种方法。您可能会认为唯一的区别是第二个不需要分配新的缓冲区,这是一个真正的区别,也是一个有意义的关键,但不是唯一的一个。如果这是唯一的区别,您会期望它可靠地更快,但这不是唯一的区别。第二种方法还需要一个额外的动态鸭子类型参数,Python 需要对其进行解析和处理。这不应该花费太多时间,但很难说它与分配 2048 字节所需的时间相比如何,这将取决于解释器使用的方法。 Python 使用全局内存池,并且在没有其他任何操作的情况下处于紧密循环中,它很可能会非常快速地一次又一次地重新分配和重新分配相同的内存,而无需调用任何操作系统函数。

这导致了下一个问题,虽然这两个操作的成本有多高很难确定(也许其他人更清楚它们中的任何一个有多大意义),但它们与网络通信的规模并不完全相同。您正在查看与毫秒式网络操作相关的纳秒/微秒式性能差异。您不仅要调用操作系统并等待 IO,而且在您接收数据的速度比发送数据的速度快的情况下,您的进程很可能会被操作系统置于睡眠状态,尤其是在您确实受到核心限制的情况下。您还提到了不一定是确定性的丢包。

如果您真的很在意这种性能规模,您应该使用 C/C++ 或 Rust 或其他允许您进行较低级别访问的语言,或者编写 C/C++ 或 Cython 模块并直接将 C 套接字库与 python 一起使用该模块(如果您的目标平台是 linux,您甚至可以使用 recvmmsg 来真正提高性能)。你可能不会。我不反对为了实验而进行实验(当你问这样一个问题时,我真的觉得很烦人,互联网上的人只是向你解释为什么不去打扰,因为你不需要它或其他什么,)所以如果那是您所学到的情况是,微优化通常几乎没有什么区别。

如果您正在尝试决定在更大的项目中使用哪种方法;如果您有任何理由为了方便而更喜欢其中一个,请使用那个。如果您真的关心性能,我会坚持使用 recv_into。即使调用不比 recv 快。如果您有一个有意义的应用程序调用该方法,那么它的内存特性将会发挥作用,并且我希望系统整体能够更好地运行,而无需所有非常小的分配和取消分配,这些分配和取消分配不太可能像您在您的系统中那样完美地排列小型基准测试循环。

编辑:只是要明确丢包在这种情况下不是确定性的,因为您系统上正在进行的其他操作没有被记录和复制……也就是说,我想说它在理论上总是确定性的但作为观察者,你实际上是不知道的。

编辑 2:我突然想到你提到禁用垃圾收集。这只会禁用收集器,但仍然会发生基于引用计数的内存释放,因此紧密的 recv 循环可能会一遍又一遍地释放和重新分配相同的内存块,因为它是由 CPython 而不是操作系统分配的,并且是少量的内存可能很快就可以完成。

编辑3:已经很晚了...无论如何我只是注意到您将所有数据包添加到recv下的列表中,因此您不会重新分配和重新分配内存,您只需将它们保留原样并存储列表结构中的内存地址应该是一个非常快速的操作。不取消分配内存意味着您不会重新使用相同的地址,但这也意味着不需要进行取消分配,并且与前往操作系统并返回以填充缓冲区。与任何操作系统启动的进程睡眠相比,这些操作也将相形见绌。

【讨论】:

以上是关于python3.6:socket.recv() 与 socket.recv_into() 性能对比的主要内容,如果未能解决你的问题,请参考以下文章

Python socket.recv() 返回新行?

socket——recv??按行读取

C++ Socket recv() 总是返回 0

linux epoll socket recv 不能获取正确的数据

使用带有 socket.recv_into 的 bytearray

python socket recv() 和信号