【问题标题】:Optimal Chunk Size to Send File in Python?在 Python 中发送文件的最佳块大小?
【发布时间】:2014-07-14 17:37:28
【问题描述】:

我实际上使用 python (2.7) 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()

我正在尝试选择最佳块大小,或者至少了解影响它的因素。该代码当前在本地千兆网络上运行,在 Ubuntu 内核 3.2 上,到 FTP 服务器的 ping 时间为 0.2 毫秒(是的,0.2 毫秒,而不是 0.2 秒)。我对 TCP 窗口缩放和发送/接收/拥塞窗口有很好的理解。我正在通过这个网络发送 2GB 的文件,并且在实践中发现传输速度随着块大小的增加而增加,使用 256KB 块大小时,传输速度最高可达 533Mb/s。作为参考,64KB 的块大小大约为 330Mb/s。

我并不是在抱怨这些速度,而是想了解为什么 256KB 块大小是最佳的。到目前为止,我发现的所有内容都表明 ~64KB 是所需的大块大小。我已经对 storebinary 函数的子组件进行了计时,以确保发送文件的总时间实际上会随着块大小增加到 256KB(而不是读取文件所花费的时间)而减少。

我传输这些 2GB 文件的代码最终将在许多网络上运行(尽管操作系统、内核、python 版本相同)。我担心 256KB 在其他网络上是次优的,我很好奇为什么 256KB 块大小可以提供最快的传输速度。任何见解将不胜感激。

编辑:对于那些关心我如何独立地为实际的 socket.sendall 调用计时的人,这里是我用来计时的函数的修改版本。从 64KB 块到 256KB 块,读取时间从 ~19s 到 ~14s,发送时间从 ~18s 到 ~10s。

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()

【问题讨论】:

  • 您可能需要考虑实现这一点,以便块大小可以动态更改,然后您可以使用启发式方法来选择合适的大小,而不必硬编码。
  • 您是从硬盘驱动器而不是 SSD 读取数据吗?您的限制因素可能是存储文件的硬盘驱动器的读取速度。
  • 作为实验,先尝试将整个文件读入内存。这将消除它成为磁盘瓶颈的可能性。
  • @JamieCockburn 如果您在我的 OP 中注意到,我提到我对该函数的实际发送部分进行了计时。当我使用 256KB 和 64KB 时,实际上花费在 socket.sendall 上的总时间确实减少了(所有 2GB 从 ~18s 到 ~10s)。更大的块大小读取也更快,但我目前专注于发送。我现在正在考虑的是,当我发送更大的块大小时,socket.send 调用会返回,而要发送的一些数据仍然由操作系统缓冲。因此,当我的程序正在读取下一个缓冲区时,操作系统正在发送。
  • @maxywb 我考虑过动态调整块大小,但我们正处于生产阶段,高层不想添加这样的更改。计划在代码的下一个版本中发布,但目前我们正试图仅根据网络速度和延迟确定一个合适的、恒定的块大小。

标签: python sockets


【解决方案1】:

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

【讨论】:

    猜你喜欢
    • 2014-09-02
    • 2019-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-19
    • 2011-04-10
    • 2011-08-03
    • 1970-01-01
    相关资源
    最近更新 更多