Python中发送文件的最佳块大小是多少?

3

我实际上是使用Python的ftplib(2.7版本)将文件发送到FTP服务器,但在底层它使用socket.sendall。下面是相关函数:

def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
    """Store a file in binary mode.  A new port is created for you.

    Args:
      cmd: A STOR command.
      fp: A file-like object with a read(num_bytes) method.
      blocksize: The maximum data size to read from fp and send over
                 the connection at once.  [default: 8192]
      callback: An optional single parameter callable that is called on
                on each block of data after it is sent.  [default: None]
      rest: Passed to transfercmd().  [default: None]

    Returns:
      The response code.
    """
    self.voidcmd('TYPE I')
    conn = self.transfercmd(cmd, rest)
    while 1:
        buf = fp.read(blocksize)
        if not buf: break
        conn.sendall(buf)
        if callback: callback(buf)

    conn.close()
    return self.voidresp()

我正在尝试选择最优的块大小,或者至少了解影响它的因素。代码目前在本地千兆网络上运行,在FTP服务器上ping时间为0.2ms(是的,0.2ms,而不是0.2s),使用Ubuntu内核3.2。我对TCP窗口缩放和发送/接收/拥塞窗口有相当的理解。我正在通过这个网络发送2GB的文件,并且实践中发现,随着块大小的增加,传输速度增加,使用256KB块大小可达到533Mb/s。参考一下,64KB的块大小大约为330Mb/s。
我并不是抱怨这些速度,但我想知道为什么256KB的块大小是最优的。到目前为止,我发现的所有信息都表明,大约需要64KB的块大小。我已经计时存储二进制函数的子组件,以确保发送文件的总时间实际上随着块大小的增加而减少,而不是花费在读取文件上的时间。
我的传输这些2GB文件的代码最终将在许多网络上运行(虽然是相同的操作系统、内核和Python版本)。我担心256KB在其他网络上是次优的,我很好奇为什么256KB的块大小可以提供最快的传输速度。任何见解都将不胜感激。
编辑:对于那些担心我如何计时实际的socket.sendall调用的人,这里是我用来计时的函数的修改版本。从64KB块到256KB块,读取时间从约19秒减少到约14秒,发送时间从约18秒减少到约10秒。
def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
    """Store a file in binary mode.  A new port is created for you.

    Args:
      cmd: A STOR command.
      fp: A file-like object with a read(num_bytes) method.
      blocksize: The maximum data size to read from fp and send over
                 the connection at once.  [default: 8192]
      callback: An optional single parameter callable that is called on
                on each block of data after it is sent.  [default: None]
      rest: Passed to transfercmd().  [default: None]

    Returns:
      The response code.
    """
    self.voidcmd('TYPE I')
    conn = self.transfercmd(cmd, rest)
    totalTime = 0
    totalSendTime = 0
    totalCallbackTime = 0
    while 1:
        startTime = time.time()
        buf = fp.read(blocksize)
        endTime = time.time()
        if not buf: break
        totalTime += (endTime - startTime)
        startTime = time.time()
        conn.sendall(buf)
        endTime = time.time()
        totalSendTime += (endTime - startTime)
        startTime = time.time()
        if callback: callback(buf)
        endTime = time.time()
        totalCallbackTime += (endTime - startTime)

    print 'Total read time was %s'%str(totalTime)
    print 'Total send time was %s'%str(totalSendTime)
    print 'Total callback time was %s'%str(totalCallbackTime)
    conn.close()
    return self.voidresp()

你可能想考虑实现动态块大小,这样你就可以使用启发式方法选择一个好的大小,而不必硬编码它。 - maxywb
你是从硬盘读取,而不是固态硬盘吗?你的限制因素可能是文件存储在硬盘上的读取速度。 - Sohcahtoa82
1
作为一项实验,尝试先将整个文件读入内存。这将消除它成为磁盘瓶颈的可能性。 - Jamie Cockburn
@JamieCockburn 如果您注意到我的原始帖子中,我提到了我计时了该函数的实际发送部分。当我使用256KB而不是64KB时,socket.sendall所花费的总时间确实减少了(对于所有2GB,从约18秒减少到约10秒)。使用较大的块大小读取速度也更快,但我目前正在关注发送。现在我正在考虑的是,当我发送较大的块大小时,socket.send调用会在一些要发送的数据仍然由操作系统缓冲时返回。因此,操作系统正在发送,而我的程序正在读取下一个缓冲区。 - user1777820
操作系统可能会预读您的读取块,因此已经存在一些同时发送和读取的情况。 - Ross Ridge
显示剩余3条评论
1个回答

1
FTP中的位基于数据报,因此它们通过固定路径以特定大小的数据包发送。要发送所有数据,您需要确定完整文件的大小,然后在FTP端期望相同大小的数据。更好的方法是在文件末尾添加结束分隔符。因此,当您在FTP端跨越文件内容循环并找到结束分隔符时,必须停止从同一客户端期望更多文件数据。将单个发送传输的位的名义大小保持在约1024左右,这是首选大小,原因有各种各样(请在Google上查找原因,您会很容易找到)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接