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

时间:2015-07-27 15:40:32

标签: python file

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

100mb

当然,在ls = [] with open("somefile.txt", "r") as f, open("otherfile.txt", "a") as w: for line in f: ls.append(line) if len(ls) == 100000: w.writelines(ls) del ls[:] 文件上工作得非常慢。

但是,我改变了程序来执行此操作

File f = new File("C:/Users/khun/testFile.txt");
try {
    channelSftp.put(new FileInputStream(f), f.getName());
} catch ...

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

3 个答案:

答案 0 :(得分:2)

我可能找到write慢于writelines的原因。在查看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);
}

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

我测试了这个,看看它是否确实有一个返回值,它确实有。

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

>>> 5

以下是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是唯一的瓶颈。

修改:write documentation

答案 1 :(得分:0)

我不同意这里的其他答案。

这简直是巧合。这在很大程度上取决于您的环境:

  • 什么操作系统?
  • 什么是硬盘/ CPU?
  • 什么是HDD文件系统格式?
  • 您的CPU / HDD有多忙?
  • 什么是Python版本?

这两段代码完全相同,性能上的微小差异。

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

我不会故意发布我的机器规格。

  

测试.write():------复制花了0.934000015259秒(破折号为可读性)

     

测试.writelines():复制花了0.936999797821秒

还测试了小而大到1.5GB的文件,结果相同。 (对于 1.5GB文件,写法总是稍慢, 0.5秒差异。)

答案 2 :(得分:-1)

这是因为在第一部分中你必须为每次迭代中的所有行调用方法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函数的时间。但是因为你使用python 3.4 in,它会一次写入一行而不是加入它们所以在这种情况下它会比write快得多:

  
      
  • cStringIO.writelines()现在接受任何可迭代的参数并写入   一次一行,而不是加入他们并写一次。   对StringIO.writelines()进行了并行更改。节省内存和   适合与生成器表达式一起使用。
  •