调用socket.recv时使用非2的幂的bufsize实际影响是什么?
在Python中,从一个socket读取数据时,你需要使用 socket.recv
这个函数,它的格式是:
socket.recv(bufsize[, flags])
Python的文档中对 socket.recv
的说明有点模糊,提到:
注意:为了更好地适应硬件和网络的实际情况,
bufsize
的值应该是一个相对较小的2的幂,比如4096。
问题:这里的“更好地适应硬件和网络的实际情况”是什么意思?如果将 bufsize
设置为不是2的幂,会有什么实际影响?
我看到过很多关于这个问题的建议,大家都推荐将这个值设为2的幂。我也知道在某些情况下,数组长度设为2的幂是有用的(比如位移/掩码操作、最佳的FFT数组大小等),但这些都是根据具体应用来决定的。我只是没看到在 socket.recv
中这样做的普遍理由。显然,Python文档中的具体建议也没有让我信服。我在Python的底层代码中也没有发现任何与2的幂相关的优化,这让我觉得这不是Python特有的建议。
举个例子……如果你有一个协议,已经确切知道了传入数据包的长度,那么显然最好只读取“最多”你需要的数据,这样就不会影响到下一个数据包,这样会很麻烦。如果我当前处理的数据包只有42个字节,我就只会将 bufsize
设置为42。
我是不是漏掉了什么?每当我需要选择一个任意的缓冲区/数组大小时,我通常(总是?)会将长度设为2的幂,以防万一。这是我多年来养成的习惯。难道Python的文档也是受这种习惯影响吗?
这并不是Python特有的,但因为我特别提到了Python的文档,所以我就标记为Python。
更新:我刚刚检查了我系统内核级别的缓冲区大小(或者说我觉得我检查了……我执行了 cat /proc/sys/net/core/rmem_default
),结果是124928。不是2的幂。 rmem_max
是131071,显然也不是2的幂。
在进一步研究这个问题后,我真的看不到推荐使用2的幂有什么好处。我快要认为这只是个无效的建议了……
我还添加了 tcp
和 C
标签,因为它们也相关。
2 个回答
关于:“如果你知道进来的数据包长度是固定的,那么最好只读取‘最多’你需要的数据,这样就不会影响到下一个数据包,这样会很烦人。”
虽然这样做可能对应用程序开发者更好,但对底层的网络系统来说可能效率不高。首先,这样会占用本来可以用来处理其他网络输入输出的socket缓冲区空间。其次,每次调用recv()都会涉及到系统调用和内核空间,这样的转换会有性能损失。通常来说,尽量从内核空间获取更多的数据到用户空间,并且减少系统调用的次数,这样在用户空间处理消息会更高效。虽然这样会让应用程序的代码和消息处理变得更复杂,但可能是最有效的做法。
不过,考虑到现在处理器的速度和可用内存的大小,这对大多数应用来说可能不是个问题,但在“老一辈”的网络应用中,这种做法是很常见的建议。
至于从用户空间应用程序来看,关于2的幂的建议我不太确定。我见过因为对齐和页面大小等问题,驱动程序会有这样的要求,但从用户空间来看,这样做的效果不太清楚,除非它能帮助将数据从内核缓冲区复制到用户缓冲区。也许有更多操作系统开发知识的人可以对此做些评论。
我觉得“使用2的幂”这个建议是因为编辑错误而产生的,不应该被当作一个必要条件。
这个具体的建议是在Python 2.5的文档中添加的(并且被回溯到Python 2.4.3的文档中),这是为了回应Python问题#756104。报告这个问题的人在使用一个不合理大的缓冲区大小给socket.recv()
,这才促使了这个更新。
是Tim Peters提出了“2的幂”这个概念:
我想你是历史上唯一一个尝试给recv()传递这么大值的人——即使它能工作,你几乎肯定会因为分配1.9GB的缓冲区空间而耗尽内存。sockets是一个低级功能,通常传递一个相对较小的2的幂(以便更好地匹配硬件和网络的实际情况)是很常见的。
(加粗部分是我强调的)。我和Tim合作过,他在网络编程和硬件方面有丰富的经验,所以一般来说,我会相信他这样说的。他特别“喜欢”Windows 95的网络栈,称它为煤矿中的金丝雀,因为它在压力下容易失败。但请注意,他说的是通常,而不是说必须使用2的幂。
正是这种措辞导致了文档的更新:
这是一个文档错误;用户应该被“警告”这件事。
这让我曾经困惑过,而且有两个人在#python中问过这个问题,所以也许我们应该在recv()的文档中加入类似以下内容。
"""
为了更好地匹配硬件和网络的实际情况,
“缓冲区”的值应该是一个相对较小的2的幂,
例如,4096。
"""如果你认为这个措辞是对的,就把这个错误分配给我,我会处理它。
没有人质疑“2的幂”这个说法,但编辑在几条回复中就把通常改成了应该。
在我看来,提出文档更新的人更关心的是确保你使用一个小的缓冲区,而不是它是否是2的幂。不过,这并不意味着这不是一个好的建议;任何与内核交互的低级缓冲区都能从与内核数据结构的对齐中受益。
但是,尽管可能存在某些特殊情况,其中缓冲区大小是2的幂更为重要,我怀疑Tim Peters曾经想让他的经验(即常见做法)被如此严格地理解。如果对于你的具体使用情况,其他缓冲区大小更合适,那就忽略这个建议吧。