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

为什么在Python中逐行复制文件会极大地影响复制速度?,python,file,Python,File,不久前,我制作了一个类似于以下内容的Python脚本: with open("somefile.txt", "r") as f, open("otherfile.txt", "a") as w: for line in f: w.write(line) 当然,在100mb的文件上运行速度非常慢 然而,我改变了程序来做这件事 ls = [] with open("somefile.txt", "r") as f, open("otherfile.txt", "a") as

不久前,我制作了一个类似于以下内容的Python脚本:

with open("somefile.txt", "r") as f, open("otherfile.txt", "a") as w:
    for line in f:
        w.write(line)
当然,在
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[:]

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

这是因为在第一部分中,您必须为每个迭代中的所有行调用方法
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,它一次只写入一行,而不是连接它们,因此在这种情况下比
write
快得多:

  • cStringIO.writelines()
    现在接受任何iterable参数并写入 一行一行地写,而不是一行接一行地写。 对StringIO.writelines()进行了并行更改。节省内存和内存 使其适用于生成器表达式

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

Modules/\u 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);
}
以下是CPython中
writelines
函数实现的代码(去掉了不相关的部分)

Modules/\u 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\u RETURN\u NONE
而不是另一个函数调用来计算写入值的大小

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

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

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


编辑:

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

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

  • 什么操作系统
  • 什么硬盘/中央处理器
  • 什么硬盘文件系统格式
  • 您的CPU/HDD有多忙
  • 什么Python版本
这两段代码做完全相同的事情,但在性能上有微小的差异

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

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

Test.write():----复制花费了0.934000015259秒(可读性用破折号)

Test.writelines():复制花费了0.93699997821秒



还使用大小为1.5GB的文件进行了测试,结果相同。(写入线总是稍微慢一些,对于1.5GB文件,最长为0.5秒

这很有趣。我认为这可能与IO操作有关
writelines
可以将字符串列表与换行符连接起来,并一次写入所有字符串。我怀疑
writelines
是否为列表/生成器中的每个元素调用
write
。我假设速度的提高来自于C语言的实现。读写之间的硬盘驱动器头搜索更少?如果您将
w.writelines(ls)
替换为
w.write(“\n”.join(ls))
?与您现有的情况相比,速度如何?您的逻辑也有点缺陷,因为您只在
len(ls)==100000:
时才编写,因此您可能会在一个文件中写入更少的行,并且
open(“otherfile.txt”,“w”,buffering=1000),因为w:
比writelines更适合我您的python版本是什么?是的,代码就是这样做的,但是你没有解释为什么第二种方法更快。但是肯定
writelines
write
做的工作更多,所以你不能只说“最好使用函数调用更少的方法”@Brobin是的,我正在源代码中寻找原因@凯文:事实上,我会用这个理由更新答案!那么,多次打开和关闭文件是否占用了时间?对于较小的文件,这种情况不会发生,因为打开和关闭的次数不多?返回字符串的长度究竟有什么不同?我的意思是,如果你运行一个常规的
返回len(line)
它是即时的!这似乎是瞬间的,但经过数千次的复合,可能需要一段时间。另外,返回长度会占用更多内存。
len()
的顺序是O(1),因此我认为这不会造成任何问题!我知道这是O(1),但这并不意味着这个计算不能成为经济放缓的原因。O(1)表示它是在线性时间内计算的。计算仍然需要时间!测试有无行长计算的源代码是个好主意吗?如果你使用更长的行,这将花费更多的时间,我认为行数也会有所不同。如果你有一个天堂般的处理器,那么,很明显你得到了结果:)你能做的最后一件事是将收集的行数更改为
1000000
或其他什么我做了许多不同的测试,包括短/长行。我的全部观点是它非常特定于环境,而不是特定于python实现(算法)。
with open('test.txt', 'w+') as f:
    x = f.writelines(["hello", "hello"])
    print(x)

>>> None