【问题标题】:readinto() replacement?readinto() 替换?
【发布时间】:2012-04-05 05:08:06
【问题描述】:

在 Python 中使用直接的方法复制文件通常是这样的:

def copyfileobj(fsrc, fdst, length=16*1024):
    """copy data from file-like object fsrc to file-like object fdst"""
    while 1:
        buf = fsrc.read(length)
        if not buf:
            break
        fdst.write(buf)

(顺便说一下,这段代码sn-p来自shutil.py)。

不幸的是,这在我的特殊用例(涉及线程和非常大的缓冲区)中存在缺点[斜体部分稍后添加]。首先,这意味着每次调用 read() 都会分配一个新的内存块,当 buf 在下一次迭代中被覆盖时,该内存被释放,只是为了相同的目的再次分配新内存。这会减慢整个过程并给主机带来不必要的负载。

为避免这种情况,我使用了 file.readinto() 方法,不幸的是,该方法被记录为已弃用且“不要使用”:

def copyfileobj(fsrc, fdst, length=16*1024):
    """copy data from file-like object fsrc to file-like object fdst"""
    buffer = array.array('c')
    buffer.fromstring('-' * length)
    while True:
        count = fsrc.readinto(buffer)
        if count == 0:
            break
        if count != len(buffer):
            fdst.write(buffer.toString()[:count])
        else:
            buf.tofile(fdst)

我的解决方案有效,但也有两个缺点:首先,不能使用 readinto()。它可能会消失(文档说)。其次,使用 readinto() 我无法决定要读入缓冲区的字节数,而使用 buffer.tofile() 我无法决定要写入多少字节,因此最后一个块的特殊情况很麻烦(这也是不必要的贵)。

我查看了 array.array.fromfile(),但它不能用于读取“所有内容”(读取,然后抛出 EOFError 并且不分发已处理项目的数量)。也不是结束特例问题的解决方案。

有没有合适的方法来做我想做的事?也许我只是忽略了一个简单的缓冲区类或类似的东西,它可以满足我的需求。

【问题讨论】:

  • 有什么原因不能使用 shutil.copyfile(src, dst) 吗?
  • Python 是一种高级语言。它已经内置了所有这些东西。不要重写它。
  • 出于特定原因,我在多线程环境中使用了非常大的缓冲区。在释放和重新分配内存之间总是有一个很好的机会,另一个线程正在搞乱内存(获得一小块大块)。在这种情况下,它真的会减慢一切,在内存中留下漏洞,并在极少数情况下最终抛出 MemoryErrors。我试图通过不分配和释放内存来避免这种情况。这就是为什么我正在寻找旧的 readinto() 的替代品。

标签: python arrays fromfile


【解决方案1】:

这段代码sn-p来自shutil.py

这是一个标准库模块。为什么不直接使用呢?

首先,这意味着每次调用 read() 都会分配一个新的内存块,当 buf 在下一次迭代中被覆盖时,该内存被释放,只是为了相同的目的再次分配新的内存。这会减慢整个过程并给主机带来不必要的负载。

与实际从磁盘中抓取一页数据所需的工作相比,这微不足道。

【讨论】:

  • 堆也可能只会给你同样的内存,因为你已经刚刚释放了它。
  • 更不用说首先通过 Python 的开销会损失多少。也就是说,CPython shutil 没有直接在 C 中实现这些东西至少有点令人惊讶,因为 Python 的实现显然是微不足道的,而且还有很多其他的方法。
  • 我已经对其进行了测量,发现在没有不必要的内存管理的情况下显着提高了性能。由于我正在开发一个复制大量数据的工具,我想等待 I/O 而不是内存管理。所以问题不在于是否使用 file.read()/file.write(),而是是否有更好的替代已弃用的 readinto()。
  • 如果没有测量值,我很难相信这一点。 CPython 以块的形式分配内存,这意味着为对象分配空间不需要任何系统调用。
  • 好的,我现在看到我上面的例子有点误导。我的真实情况更复杂。它在一个线程中读取一个大块(~10MB)并在另一个线程中写入;其他线程也分配内存。这会留下漏洞。无论如何,不​​要太接近这个例子。我正在寻找 readinto() 的替代品,用于读取现有缓冲区,也可能用于从该缓冲区写入。这个通用用例对我来说是如此明显,以至于我很惊讶(除了已弃用的版本)在标准 python 库中没有任何内容。
【解决方案2】:

普通的 Python 代码不需要这样的调整 - 但是,如果你真的需要所有的性能调整来从 Python 代码中读取文件(例如,你正在重写你编写的一些服务器 coe,并且已经可以工作)为了性能或内存使用)我宁愿直接使用 ctypes 调用操作系统 - 因此也可以根据需要执行低级别的副本。

在您的情况下,将“cp”可执行文件简单地调用为外部进程甚至可能不是一个障碍(它会为您充分利用所有操作系统和文件系统级别的优化)。

【讨论】:

  • 好主意。它会降低我的可移植性(例如到 Windows)。但我可能会考虑这个,谢谢:-)