Python readlines()用法和有效的阅读练习

Lea*_*ner 38 python memory performance python-2.6 readlines

我有一个问题是在文件夹中解析1000个文本文件(每个文件大约3000行,大小约400KB).我确实用readlines读过它们,

   for filename in os.listdir (input_dir) :
       if filename.endswith(".gz"):
          f = gzip.open(file, 'rb')
       else:
          f = open(file, 'rb')

       file_content = f.readlines()
       f.close()
   len_file = len(file_content)
   while i < len_file:
       line = file_content[i].split(delimiter) 
       ... my logic ...  
       i += 1  
Run Code Online (Sandbox Code Playgroud)

这对我输入的样本(50,100个文件)完全没问题.当我在整个输入上运行超过5K的文件时,所花费的时间远不及线性增量.我计划进行性能分析并进行Cprofile分析.当输入达到7K文件时,更多文件以指数方式增加并且达到更差的速率所花费的时间.

这是readlines的累计时间,第一个 - > 354个文件(来自输入的样本)和第二个 - > 7473个文件(整个输入)

 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 354    0.192    0.001    **0.192**    0.001 {method 'readlines' of 'file' objects}
 7473 1329.380    0.178  **1329.380**    0.178 {method 'readlines' of 'file' objects}
Run Code Online (Sandbox Code Playgroud)

因此,我的代码所花费的时间不会随着输入的增加而线性缩放.我阅读了一些文档说明readlines(),其中人们声称这readlines()会将整个文件内容读入内存,因此与readline()或相比通常消耗更多内存read().

我同意这一点,但是垃圾收集器是否应该在循环结束时自动从内存中清除加载的内容,因此在任何时刻我的内存应该只包含当前处理文件的内容?但是,这里有一些问题.有人可以对这个问题有所了解.

这是readlines()python垃圾收集器的固有行为或我的错误解释.很高兴知道.

此外,建议一些替代方法,以内存和时间效率的方式做同样的事情.TIA.

aba*_*ert 79

简短版本是:有效的使用方法readlines()是不使用它.永远.


我阅读了一些文档说明readlines(),其中人们声称这readlines()会将整个文件内容读入内存,因此与readline()或read()相比通常会消耗更多内存.

该文档明确保证它将整个文件读入内存,并将其解析为行,并从这些行中构建完整的文件.readlines() liststr

read()同样的文档保证它将整个文件读入内存,并构建一个string,这样做无济于事.


除了使用更多内存之外,这也意味着在读取整个内容之前,您无法完成任何工作.如果您以最天真的方式交替阅读和处理,您将受益于至少一些流水线操作(由于操作系统磁盘缓存,DMA,CPU管道等),因此您将在下一批中处理一个批处理正在阅读.但是,如果您强制计算机读取整个文件,然后解析整个文件,然后运行您的代码,您只能获得整个文件的一个重叠区域,而不是每个读取重叠工作的一个区域.


您可以通过以下三种方式解决此问题:

  1. 周围写一个循环readlines(sizehint),read(size)readline().
  2. 只需将该文件用作惰性迭代器而不调用其中任何一个.
  3. mmap 该文件,允许您在没有先阅读的情况下将其视为一个巨大的字符串.

例如,这必须立即读取所有内容foo:

with open('foo') as f:
    lines = f.readlines()
    for line in lines:
        pass
Run Code Online (Sandbox Code Playgroud)

但这一次只读取大约8K:

with open('foo') as f:
    while True:
        lines = f.readlines(8192)
        if not lines:
            break
        for line in lines:
            pass
Run Code Online (Sandbox Code Playgroud)

而且这一次只能读取一行 - 虽然Python允许(并且将)选择一个漂亮的缓冲区大小以使事情更快.

with open('foo') as f:
    while True:
        line = f.readline()
        if not line:
            break
        pass
Run Code Online (Sandbox Code Playgroud)

这将完成与前一个完全相同的事情:

with open('foo') as f:
    for line in f:
        pass
Run Code Online (Sandbox Code Playgroud)

与此同时:

但是垃圾收集器是否应该在循环结束时自动从内存中清除加载的内容,因此在任何时刻我的内存应该只包含当前处理文件的内容?

Python没有对垃圾收集做出任何此类保证.

CPython实现碰巧使用refcounting for GC,这意味着在你的代码中,只要file_content反弹或消失,巨大的字符串列表及其中的所有字符串将被释放到freelist,意思是相同的内存可以再次重复用于下一次传递.

但是,所有这些分配,复制和解除分配都不是免费的 - 不执行它们比执行它们要快得多.

最重要的是,让你的字符串分散在一大块内存中,而不是一遍又一遍地重复使用相同的小块内存会损害你的缓存行为.

另外,虽然内存使用量可能是恒定的(或者说,相当于最大文件大小的线性,而不是文件大小的总和),malloc但是第一次扩展它的速度将是最慢的你做的事情(这也使得进行性能比较变得更加困难).


总而言之,这就是我编写程序的方式:

for filename in os.listdir(input_dir):
    with open(filename, 'rb') as f:
        if filename.endswith(".gz"):
            f = gzip.open(fileobj=f)
        words = (line.split(delimiter) for line in f)
        ... my logic ...  
Run Code Online (Sandbox Code Playgroud)

或者可能:

for filename in os.listdir(input_dir):
    if filename.endswith(".gz"):
        f = gzip.open(filename, 'rb')
    else:
        f = open(filename, 'rb')
    with contextlib.closing(f):
        words = (line.split(delimiter) for line in f)
        ... my logic ...
Run Code Online (Sandbox Code Playgroud)


Ósc*_*pez 16

逐行读取,而不是整个文件:

for line in open(file_name, 'rb'):
    # process line here
Run Code Online (Sandbox Code Playgroud)

更好地with用于自动关闭文件:

with open(file_name, 'rb') as f:
    for line in f:
        # process line here
Run Code Online (Sandbox Code Playgroud)

上面将使用迭代器读取文件对象,一次一行.

  • @ÓscarLópez:重新计算的重点是_it不必run_.每次引用消失(例如,名称被反弹或超出范围)时,引用对象的计数减少,如果它达到0,则对象被回收_immediately_.(循环检测是另一个更复杂的故事,但同样,这不是与此有关,因为有在OP的代码中没有循环.)的[Wikipedia文章](http://en.wikipedia.org/wiki/Reference_counting)解释得很好. (3认同)
  • @abarnert感谢澄清,我学到了新东西:) (2认同)