为什么在Python中逐行复制文件会极大地影响复制速度?

2024-04-20 10:04:03 发布

您现在位置:Python中文网/ 问答频道 /正文

不久前,我制作了一个Python脚本,类似于:

with open("somefile.txt", "r") as f, open("otherfile.txt", "a") as w:
    for line in f:
        w.write(line)

当然,它在100mb文件上运行得相当慢。在

但是,我改变了程序来做这个

^{pr2}$

文件的复制速度更快。我的问题是,为什么第二种方法工作得更快,即使程序复制了相同数量的行(尽管它收集它们并逐个打印它们)?在


Tags: 文件in程序txt脚本foraswith
3条回答

我可能找到了writewritelines慢的原因。在查看CPython源代码(3.4.3)时,我找到了write函数的代码(去掉了无关部分)。在

Modules/_io/fileio.c

static PyObject *
fileio_write(fileio *self, PyObject *args)
{
    Py_buffer pbuf;
    Py_ssize_t n, len;
    int err;
    ...
    n = write(self->fd, pbuf.buf, len);
    ...

    PyBuffer_Release(&pbuf);

    if (n < 0) {
        if (err == EAGAIN)
            Py_RETURN_NONE;
        errno = err;
        PyErr_SetFromErrno(PyExc_IOError);
        return NULL;
    }

    return PyLong_FromSsize_t(n);
}

如果您注意到了,这个函数实际上返回一个值,即已写入的字符串的大小,另一个函数调用。在

我测试了一下,看看它是否真的有返回值,它确实有。在

^{pr2}$

下面是CPython中writelines函数实现的代码(去掉无关部分)。在

Modules/_io/iobase.c

static PyObject *
iobase_writelines(PyObject *self, PyObject *args)
{
    PyObject *lines, *iter, *res;

    ...

    while (1) {
        PyObject *line = PyIter_Next(iter);
        ...
        res = NULL;
        do {
            res = PyObject_CallMethodObjArgs(self, _PyIO_str_write, line, NULL);
        } while (res == NULL && _PyIO_trap_eintr());
        Py_DECREF(line);
        if (res == NULL) {
            Py_DECREF(iter);
            return NULL;
        }
        Py_DECREF(res);
    }
    Py_DECREF(iter);
    Py_RETURN_NONE;
}

如果你注意到了,没有返回值!它只是使用Py_RETURN_NONE而不是另一个函数调用来计算写入值的大小。在

所以,我继续测试,确实没有返回值。在

with open('test.txt', 'w+') as f:
    x = f.writelines(["hello", "hello"])
    print(x)

>>> None

write花费的额外时间似乎是由于实现中为生成返回值而进行的额外函数调用。通过使用writelines,您跳过了这一步,fileio是唯一的瓶颈。在

编辑:^{} documentation

这是因为在第一部分中,您必须为每个迭代中的所有行调用方法write,这使得您的程序需要花费大量的时间来运行。但是在第二个代码中虽然浪费了更多的内存,但是它的性能更好,因为您每100000行都调用了writelines()方法。在

让我们看看这是源,这是writelines函数的源:

def writelines(self, list_of_data):
    """Write a list (or any iterable) of data bytes to the transport.

    The default implementation concatenates the arguments and
    calls write() on the result.
    """
    if not _PY34:
        # In Python 3.3, bytes.join() doesn't handle memoryview.
        list_of_data = (
            bytes(data) if isinstance(data, memoryview) else data
            for data in list_of_data)
    self.write(b''.join(list_of_data))

如您所见,它连接所有列表项并调用write函数一次。在

注意,在这里加入数据需要花费时间,但是它比为每个数据调用write函数的时间要短行。但是由于您在中使用了Python3.4,因此它一次只写一行,而不是将它们连接起来,因此在本例中它将比write快得多:

  • cStringIO.writelines() now accepts any iterable argument and writes the lines one at a time rather than joining them and writing once. Made a parallel change to StringIO.writelines(). Saves memory and makes suitable for use with generator expressions.

我不同意这里的另一个答案。在

这只是一个巧合。这在很大程度上取决于您的环境:

  • 什么操作系统?在
  • 硬盘驱动器/CPU?在
  • 什么硬盘文件系统格式?在
  • 你的CPU/HDD有多忙?在
  • 什么Python版本?在

这两段代码执行完全相同的操作,但在性能上有细微差别。在

就我个人而言,.writelines()比使用.write()执行第一个示例要花更长的时间。用110MB文本文件测试。在

我不会故意张贴我的机器规格。

Test .write(): copying took 0.934000015259 seconds (dashes for readability)

Test .writelines(): copying took 0.936999797821 seconds

同时也测试了大小为1.5GB的文件,结果相同。(对于1.5GB的文件,0.5GB差异最大0.5秒)。在

相关问题 更多 >