readinto()的替代方案是什么?

4

使用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)

(顺便说一下,这段代码片段来自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并不返回已处理项的数量)。此外,它也无法解决结束特殊情况的问题。
是否有一种正确的方法来实现我想要做的事情?也许我只是忽略了一个简单的缓冲区类或类似的东西,可以满足我的需求。

1
你不能使用shutil.copyfile(src, dst)的原因是什么? - Maria Zverina
2
Python是一种高级语言。它已经内置了所有这些东西,不要重写它。 - Katriel
由于特定原因,我在多线程环境中使用非常大的缓冲区。释放内存并重新分配内存之间总有一个很好的机会,另一个线程会破坏内存(获取大块的一小部分)。在这种情况下,它会减慢一切,留下内存空洞,并最终在罕见的情况下引发MemoryErrors。我试图通过不分配和释放内存来避免这种情况。这就是为什么我正在寻找旧readinto()的替代品。 - Alfe
2个回答

5

这段代码摘自shutil.py,该模块是标准库中的一个模块。为什么不直接使用它呢?

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

与从磁盘获取数据页面所需的工作相比,这些仅仅是微不足道的。


6
由于你刚刚释放了内存,所以堆很可能会将相同的内存再次返回给你。 - Katriel
更不用说通过Python进行开销而损失多少了。话虽如此,至少有一点令人惊讶的是,鉴于Python实现显然很简单,而且其他很多东西都是这样做的,CPython的'shutil'没有直接在C中实现这些东西。 - Karl Knechtel
1
我进行了测量,发现没有必要进行内存管理可以显著提高性能。由于我正在开发一个用于拷贝大量数据的工具,因此我想等待I/O而不是进行内存管理。因此,问题不在于是否使用file.read()/file.write(),而在于是否存在更好的替代readinto()函数。 - Alfe
我觉得没有数据支持很难相信。CPython以块的形式分配内存,这意味着为对象分配空间不需要进行任何系统调用。 - Björn Lindqvist
好的,我现在意识到我上面的例子有点误导人了。我的实际情况更为复杂。它在一个线程中读取一个大块(~10MB)并在另一个线程中写入;其他线程也会分配内存。这可能会留下空洞。无论如何,不要过于关注这个例子。我正在寻找一个替换readinto()用于将数据读入现有缓冲区的方法,或者也可以用于从该缓冲区写入数据。这个一般性的用例对我来说非常明显,我很惊讶(除了已弃用的版本外)标准python lib中没有为此提供任何内容。 - Alfe

3

通常情况下,Python代码不需要进行此类微调 - 但是如果您真的需要所有性能微调来从Python代码内部读取文件(例如,您正在重写一些已经为性能或内存使用而工作的服务器代码),我宁愿直接使用ctypes调用操作系统 - 从而使副本执行得尽可能低级。

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


不错的想法。这可能会降低我的可移植性(比如到Windows)。但我会考虑一下的,谢谢 :-) - Alfe

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