【问题标题】:What is the actual impact of calling socket.recv with a bufsize that is not a power of 2?使用不是 2 的幂的 bufsize 调用 socket.recv 的实际影响是什么?
【发布时间】:2011-09-15 20:26:15
【问题描述】:

要从 python 中的套接字读取数据,请调用 socket.recv,它具有以下签名:

socket.recv(bufsize[, flags])

python docs for socket.recv 模糊状态:

注意:为了与硬件和网络现实进行最佳匹配, bufsize 应该是比较小的 2 的幂,例如 4096。

问题:“与硬件和网络现实最匹配”是什么意思?将 bufsize 设置为非二次方的实际影响是什么?

我已经看到 many other recommendations 使其读取为 2 的幂。我也很清楚将数组长度作为 2 的幂通常有用的原因(位移/屏蔽长度、最佳 FFT 数组大小等的操作),但这些取决于应用程序。我只是没有看到socket.recv 的一般原因。当然不是python文档中的specific recommendation。我也没有在 underlying python code 中看到任何二次幂优化以使其成为特定于 python 的推荐

例如...如果您有一个确切知道传入数据包长度的协议,显然最好只读取“最多”您正在处理的数据包所需的内容,否则您可能会吃掉下一个数据包,那会很烦人。如果我当前正在处理的数据包只有 42 个字节待处理,我只会将 bufsize 设置为 42。

我错过了什么?当我必须选择任意缓冲区/数组大小时,我通常(总是?)将长度设为 2 的幂,以防万一。这只是多年养成的习惯。 python 文档也只是习惯的受害者吗?

这不是 python 独有的,但由于我专门引用了 python 文档,所以我会这样标记它。


更新:我刚刚在我的系统上检查了内核级别的缓冲区大小(或者至少我认为我做了...我做了cat /proc/sys/net/core/rmem_default),它是 124928。不是两个的幂。 rmem_max 是 131071,显然也不是 2 的幂。

在进一步研究这一点时,我真的看不出两个推荐的力量有什么好处。我准备把它称为虚假推荐......

我还添加了tcpC 标签,因为它们也很相关。

【问题讨论】:

  • 我最好的猜测是,出于内存管理的原因,一些操作系统级别的网络堆栈可能更喜欢维护 2 次幂大小的缓冲区,但这似乎是一个毫无意义的历史产物。也许这个笔记的主旨是缓冲区大小应该是 4KB 而不是 4MB 或 4GB,2 的幂只是迷信。
  • 我的猜测是,非 2 的幂大小唯一可能的真正后果是某些较低级别的代码可能会分配一个缓冲区,其大小无论如何都是 2 的下一个幂,并且如果缓冲区不会被使用,则为后一部分。除非您的 RAM 严重不足,否则这不是问题...
  • 查看this SO question 上的答案。它也处理recv(),答案可能有助于更全面地理解。它不会回答问题,但会提供更多见解。
  • 很可能唯一可能重要的数字是:高速缓存行大小(通常为 64 字节,但始终为 2 的幂)和页面大小(通常为 4096 字节,但始终为 2 的幂)。只要您是其中更相关的倍数,您可能就是理想的。
  • 还要注意,由于缓存关联的怪异,最好不要使用 2 的幂。

标签: python c sockets tcp


【解决方案1】:

我很确定“2 的幂”建议是基于编辑错误,不应被视为要求

那条具体的建议是 added to the Python 2.5 documentation(和 backported to Python 2.4.3 docs),是对 Python issue #756104 的回应。记者为socket.recv() 使用了不合理的大缓冲区,这促使更新。

是 Tim Peters 提出了“2 的力量”概念:

我希望你是历史上唯一一个尝试通过这样的人 recv() 的一个很大的价值——即使它有效,你也几乎 尝试分配缓冲区空间肯定会耗尽内存 1.9GB。套接字是一种低级设施,通常用于 通过 2 的相对较小的幂(为了与 硬件和网络现实)。

(我的粗体强调)。我和 Tim 一起工作过,他在网络编程和硬件方面拥有丰富的经验,所以一般来说,当我发表这样的评论时,我会相信他的话。他特别“喜欢”Windows 95 堆栈,他称其为煤矿中的金丝雀,因为它能够在压力下失败。但请注意,他说这是常见的,而不是必须使用 2 的幂。

正是这种措辞导致了文档更新:

这是一个文档错误;用户应该是的东西 “警告”。

这引起了我一次,两个不同的人问 这个在#python中,所以也许我们应该放一些类似的东西 遵循 recv() 文档。

"""
为了与硬件和网络现实进行最佳匹配,
“缓冲区”的值应该是 2 的相对较小的幂,
例如,4096。
"""

如果您认为措辞正确,只需将错误分配给 我,我会处理的。

这里没有人挑战“2 的幂”断言,但编辑从 it is common 移动到 should be 在几个回复的空间中。 p>

对我来说,那些提议更新文档的人更关心的是确保您使用一个小缓冲区,而不是它是否是 2 的幂。这并不是说它不好建议 但是;任何与内核交互的低级缓冲区都受益于与内核数据结构的对齐。

但是,尽管很可能存在一个深奥的堆栈,其中大小为 2 次方的缓冲区更为重要,但我怀疑 Tim Peters 是否曾为他的经验而存在(这是常见的做法)以这种铁定的方式铸造。如果不同的缓冲区大小对您的特定用例更有意义,请忽略它。

【讨论】:

  • 很好的答案和一段有趣的历史。谢谢!!
  • 是的,“2 的幂”在这里是一条红鲱鱼。一个特定的套接字实现可能会或可能不会支持特定的缓冲区大小而不是其他的,但“错误”报告解决方案的真正意义在于阻止发布者尝试使用数 GB 的缓冲区。顺便说一句,Win95 的价值在于发现 Python(和 Zope)中的错误。它的线程和套接字很好,但与当时的 Unix-y 系统相比,它的语用学(如计时)完全不同。在 Win95 上运行压力测试发现了一个合法错误的世界!
【解决方案2】:

关于:“如果您有一个确切知道传入数据包长度的协议,显然最好只读取“最多”您正在处理的数据包所需的内容,否则您可能会吃掉下一个数据包,那会很烦人。”

这对于应用程序开发人员可能更可取,但对于底层网络堆栈可能效率低下。首先,它占用了可用于额外网络 I/O 的套接字缓冲区空间。其次,您所做的每个 recv() 都意味着进入系统调用/内核空间,并且转换会降低性能。总是希望尽可能多地从内核空间获取尽可能多的数据,并使用尽可能少的系统调用进入用户空间,并在那里进行消息解析。这增加了应用程序代码和消息处理的复杂性,但可能是最有效的。

也就是说,考虑到当今处理器的速度和可用内存量,这对于大多数应用程序来说可能不是问题,但这是“过去”网络应用程序的常见建议。

我不确定来自用户空间应用程序的 2 推荐的威力。由于对齐和页面大小问题等,我已经看到驱动程序的这些类型要求,但不清楚这对用户空间有什么影响,除非它以某种方式帮助将数据从内核缓冲区复制到用户缓冲区。也许有更多操作系统开发知识的人可以发表评论。

【讨论】:

  • 很好的回应!但是... python 的bufsize 参数与底层内核级套接字缓冲区没有任何关系。 bufsize 只是分配给内核 recv 调用以填充读取字节的缓冲区(python 字符串)的大小。即:bufsize 不应“对底层网络堆栈效率低下”。尽管它确实强调了如果 socket.recv 过早返回(缓慢/零散地到达传入数据??),用户空间缓冲区分配可能过多。我内核缓冲区包含所有字节,它将内核 recv 调用限制为一个,这很好。
  • 我还感兴趣的是,我刚刚检查了(我认为)我系统上内核 recv 缓冲区的大小,这是一个奇怪的值...... 124928。我将用既然非二的幂似乎也相关。
  • 是的,recv() bufsize 与内核缓冲区大小无关。效率低下在于 (a) 将数据留在内核缓冲区中意味着内核在任何时间点可用的网络缓冲区空间较少。理想情况下,您希望释放内核缓冲区并在用户缓冲区中执行消息解析,并且 (b) 无关的系统调用效率低下。假设有 2 条 42 字节的消息发送给您并等待。最好执行一个 recv(),获取 84 个字节并解析缓冲区中的消息,而不是 2 个 42 个 recv()。对于两条消息,这无关紧要,但会成为规模问题。
  • 我现在明白了...感谢您的澄清。我对过度阅读和在应用程序级别使用第二层缓冲进行解析的想法感到畏缩,但这主要源于有时令人讨厌的字符串不变性导致额外的空间使用。也许我会摆弄它并使用 python 缓冲区(我猜现在称为 memoryview ......就像新名称一样)来尝试避免这种情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-15
  • 1970-01-01
  • 2015-06-24
  • 2013-10-06
  • 1970-01-01
  • 2011-04-02
相关资源
最近更新 更多