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

3 投票
1 回答
2627 浏览
提问于 2025-04-18 13:17

我实际上是在用Python的ftplib库把文件发送到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服务器的延迟是0.2毫秒(没错,是0.2毫秒,不是0.2秒),操作系统是Ubuntu 3.2。我对TCP的窗口缩放、发送/接收/拥塞窗口有一定的了解。我正在这个网络上发送2GB的文件,实际测试发现,随着块大小的增加,传输速度也在提升,使用256KB的块大小时速度达到了533Mb/s。作为参考,64KB的块大小大约能达到330Mb/s。

我并不是在抱怨这些速度,但我想理解为什么256KB的块大小是最优的。我找到的资料都表明大约64KB的块大小就足够了。我还对storebinary函数的各个子组件进行了计时,确保发送文件的总时间确实随着块大小的增加而减少,直到256KB(而不是读取文件的时间)。

我用来传输这些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()

1 个回答

1

在ftp中,数据是以数据包的形式发送的,也就是说,它们会通过固定的路径以特定大小的包进行传输。要发送所有数据,你需要先确定整个文件的大小,然后在ftp端也要期待这个大小。更好的方法是在文件的末尾加一个结束标记。这样,当你在ftp端遍历文件内容时,一旦找到这个结束标记,就可以停止期待来自同一个客户端的更多数据。通常,每次发送的数据包大小保持在大约1024字节,这个大小是比较理想的,原因有很多(你可以在谷歌上查一下,应该能很容易找到)。

撰写回答